Processing...
Description & Source Code
  • Description
  • View
    websocket.zul
    ctrl.zul
  • View Model
    RobotTrackViewModel.java
    ServerPushCtrl.java
    Coord.java

This simple example allows to compare the benefits of a WebSocket based Server Push approach with the Comet method. In ZK 8.5 the UI Engine can be configured to use WebSockets.. Once enabled, updates triggered by user e.g. an "onClick"-Event or server-side triggered updates (Server Push) can use this persistent WebSocket channel.

In order to see the difference between Comet and WebSocket Server Push you can inspect the current network activity in your browser's developer tools before and after changing the Server Push Mode above - e.g. in Chrome Command+Option+I (Mac) or Control+Shift+I (Windows, Linux). While the UI Updates result in the same DOM Updates - you WILL notice a significant reduction in the network activity when using WebSocket.

websocket.zul
<?taglib uri="http://www.zkoss.org/dsp/web/core" prefix="c"?>
<zk>
	<style src="/widgets/server_push/websocket/style.css.dsp"/>
	<div viewModel="@id('vm') @init('demo.server_push.websocket.RobotTrackViewModel')">
		<hlayout valign="middle">
			<button label="@load(vm.running ? 'Stop' : 'Start')"
					onClick="@command(vm.running ? 'stop' : 'start')"/>
			<separator orient="horizontal"/>
			Push Interval:
			<slider minpos="200" maxpos="2000" curpos="500" slidingtext="{0} ms" width="160px"
					onScroll="@global-command('updateInterval', interval=event.pos)"/>
		</hlayout>
		<separator/>
		<hlayout>
			<div sclass="mapArea">
				<div sclass="@load(('robot' += (vm.running ? '' : ' idle'))) "
					 top="@load((vm.position.y += '%'))"
					 left="@load((vm.position.x += '%'))"/>
			</div>
			<div>
				X: <decimalbox readonly="true" value="@load(vm.position.x)" format="#.000" width="55px"/>
				<separator/>
				Y: <decimalbox readonly="true" value="@load(vm.position.y)" format="#.000" width="55px"/>
			</div>
		</hlayout>
	</div>
</zk>
			
ctrl.zul
<vlayout apply="demo.server_push.websocket.ServerPushCtrl">
	<groupbox  title="Server Push Mode">
		<radiogroup id="serverPushMode" orient="vertical">
			<radio value="websocket" label="WebSocket (new in 8.5)" checked="true"/>
			<separator/>
			<radio value="comet" label="Comet"/>
			<separator/>
		</radiogroup>
	</groupbox>
</vlayout>
RobotTrackViewModel.java
package demo.server_push.websocket;

import org.zkoss.bind.BindUtils;
import org.zkoss.bind.annotation.*;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Executions;

import java.util.concurrent.TimeUnit;
import java.util.concurrent.atomic.AtomicBoolean;

public class RobotTrackViewModel {
	private static final String RUNNING = "running";
	public static final String POSITION = "position";
	public static final String INTERVAL = "interval";
	public static final String UPDATE_INTERVAL = "updateInterval";

	private Coord position = new Coord(50, 50);
	private AtomicBoolean running = new AtomicBoolean(false);
	private int interval = 500;

	@Command
	@NotifyChange(RUNNING)
	public void start(@ContextParam(ContextType.DESKTOP) Desktop desktop) {
		updatePosition();
		this.running.set(true);
		startBackgroundThread(desktop);
	}

	@Command
	@NotifyChange(RUNNING)
	public void stop(@ContextParam(ContextType.DESKTOP) Desktop desktop) {
		this.running.set(false);
	}

	@GlobalCommand(UPDATE_INTERVAL)
	@NotifyChange(INTERVAL)
	public void updateInterval(@BindingParam("interval") int interval) {
		this.interval = Math.min(Math.max(interval, 200), 2000);
	}

	private void startBackgroundThread(Desktop desktop) {
		new Thread(() -> {
			while (running.get()) {
				try {
					activated(desktop, () -> {
						this.updatePosition();
					});
					Thread.sleep(interval);
				} catch(Exception e) {
					//leave background thread, no error handling in this Demo
					return;
				}
			}
		}).start();
	}

	private void activated(Desktop desktop, Runnable task) {
		try {
			Executions.activate(desktop);
			task.run();
		} catch (Exception e) {
			throw new RuntimeException(e);
		} finally {
			Executions.deactivate(desktop);
		}
	}

	private void updatePosition() {
		double now = (double) System.nanoTime() / TimeUnit.SECONDS.toNanos(1);
		this.position = new Coord(Math.cos(now / Math.PI / 2.0) * 40 + 50, Math.sin(now / Math.PI / 2.0) * 40 + 50);
		BindUtils.postNotifyChange(null, null, this, POSITION);
	}

	public boolean getRunning() {
		return running.get();
	}

	public Coord getPosition() {
		return position;
	}
}
ServerPushCtrl.java
package demo.server_push.websocket;

import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.Desktop;
import org.zkoss.zk.ui.Execution;
import org.zkoss.zk.ui.Executions;
import org.zkoss.zk.ui.event.CheckEvent;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.sys.DesktopCtrl;
import org.zkoss.zk.ui.util.Clients;
import org.zkoss.zkmax.au.websocket.WebSocketServerPush;
import org.zkoss.zkmax.ui.comet.CometServerPush;
import org.zkoss.zul.Radio;

import javax.servlet.http.HttpServletRequest;

/**
 * This controller allows toggling between websocket and comet mode. ONLY for demonstration purposes.
 * In a real application the fallback to comet will happen automatically if websockets are not available
 */
public class ServerPushCtrl extends SelectorComposer {

	@Override
	public void doAfterCompose(Component comp) throws Exception {
		super.doAfterCompose(comp);
		Desktop desktop = Executions.getCurrent().getDesktop();
		desktop.enableServerPush(true);
		DesktopCtrl desktopCtrl = (DesktopCtrl) desktop;
	}

	@Listen("onCheck=#serverPushMode")
	public void toggleWebsocket(CheckEvent event) {
		Execution execution = Executions.getCurrent();
		Desktop desktop = execution.getDesktop();
		DesktopCtrl desktopCtrl = (DesktopCtrl) desktop;

		boolean enabled = event.isChecked();
		String mode = ((Radio)event.getTarget()).getValue();

		switch (mode) {
			case "comet": {
				desktop.enableServerPush(false);
				((DesktopCtrl) desktop).enableServerPush(new CometServerPush());
				Clients.evalJavaScript("zWs.stop()");
				break;
			}
			case "websocket": {
				desktop.enableServerPush(false);
				((DesktopCtrl)desktop).enableServerPush(new WebSocketServerPush());
				String wsurl = desktop.getWebApp().getAttribute("websocketUrl").toString();
				boolean secure = ((HttpServletRequest) execution.getNativeRequest()).isSecure();
				Clients.evalJavaScript(String.format("zWs.start('%s', %s);", wsurl, secure));
				break;
			}
		}
	}
}
Coord.java
package demo.server_push.websocket;

public class Coord {
	private double x;
	private double y;

	public Coord(double x, double y) {
		this.x = x;
		this.y = y;
	}

	public double getX() {
		return x;
	}

	public double getY() {
		return y;
	}
}