Template Examples - Locker"

From Documentation
Line 133: Line 133:
  
  
== UiLockable/MvvmLockable ==
+
== Lockable/MvvmLockable ==
  
 
In order to "calculate" the lock status the UiLockable needs a ''resource'', the current user (''self'') and the lock ''owner'' information.
 
In order to "calculate" the lock status the UiLockable needs a ''resource'', the current user (''self'') and the lock ''owner'' information.
Line 139: Line 139:
  
 
<source lang="java">
 
<source lang="java">
public class UiLockable<T> {
+
public class Lockable<T> {
 
private final String self;
 
private final String self;
 
private final T resource;
 
private final T resource;
Line 175: Line 175:
  
 
<source lang="java">
 
<source lang="java">
public class MvvmLockable<T> extends UiLockable<T> {
+
public class MvvmLockable<T> extends Lockable<T> {
 
...
 
...
 
@Override
 
@Override

Revision as of 03:11, 2 January 2018

DocumentationSmall Talks2018JanuaryTemplate Examples - Locker
Template Examples - Locker

Author
Robert Wenzel, Engineer, Potix Corporation
Date
January XX, 2018
Version
ZK 8.5

Introduction

What we want

Our goal is to avoid multiple users from editing the same resource simultaneously we need the following functionalities.

  1. lock / unlock a resource
    e.g. obtain and release exclusive access to edit a specific objects properties
  2. observe a resource for owner changes (LockEvents)
    it should be possible to observe the same resource with multiple concurrent users
  3. react to LockEvents and update the UI
    immediately indicate availability/ownership/unavailability of a resource to each observing user

First we need a way to indicate our intention that we want to edit a resource and obtain a lock and vice versa return the lock (unlock) when we are done editing. This is only useful if other users are aware of that condition so they don't attempt concurrent editing (and potentially losing data or producing inconsistent merged results) - means we need to observe the current lock status and finally react in the UI. e.g. by changing labels, styles, disabling/hiding/removing/adding appropriate controls.

Simple example

Before going into details here a small example illustrating what we are trying to achieve.

Here 2 users in 2 separate browsers are seeing (observing) the same resource - initially not locked by anyone. As soon as User-1 locks the resource it becomes editable for him. At the same the lock button disappears for the User-2 - replaced by a message showing the lock status and owner. When unlocking the resource it becomes available for both users again. Then User-2 repeats the cycle. As simple as that.

How does it work

The example above is implemented in simpleLockable.zul and SimpleLockableViewModel.java shouldn't contain too many surprises - Which is the intention of this article -> to show a simple way to achieve that.

simpleLockable.zul

<zk>
	<style>
		.lockIndicator { padding: 12px; display: inline-block; border-radius: 15px; }
		.lockIndicator .z-label { color: white; font-weight: bold; }
		.lockIndicator.OWNED { background-color: LimeGreen; }
		.lockIndicator.AVAILABLE { background-color: LightSteelBlue; }
		.lockIndicator.UNAVAILABLE { background-color: Crimson; }
	</style>
	<div viewModel="@id('vm') @init('zk.example.template.locker.SimpleLockableViewModel')">
		I am '${vm.username}'.
		<separator/>

		This is
		<div sclass="@load(('lockIndicator ' += vm.lockable.status))">
			<choose>
				<when test="@load(vm.lockable.status eq 'OWNED')">
					<textbox value="@bind(vm.lockable.resource.value)"/>
				</when>
				<otherwise>
					<label value="@load(vm.lockable.resource.value)"/>
				</otherwise>
			</choose>
		</div>

		<apply template="@load(vm.lockable.status)">

			<template name="OWNED">
				I am the owner. No one except me can edit until I
				<button iconSclass="z-icon-unlock" label="unlock" onClick="@command('unlock')"/> it.
				...
			</template>

			<template name="AVAILABLE">
				No one has currently locked it. I can
				<button iconSclass="z-icon-lock" label="lock" onClick="@command('lock')"/> it.
				...
			</template>

			<template name="UNAVAILABLE">
				currently locked by '${vm.lockable.owner}'.
				...
			</template>

		</apply>
	</div>
</zk>
  • Line 14: dynamic property sclass changing by lock status
  • Lines 16,19: render the resource editable for status OWNED (using the shadow elements <choose>/<when>/<othewise> LINK ME)
  • Lines 25,27,33,39: apply (LINK ME) dynamic templates to add controls/information for all 3 different lock states

In the zul file above all the render decisions are based on the EL vm.lockable.status for now it's sufficient to know that this status property will "somehow" contain and update the current lock status (synchronized for all users).

SimpleLockableViewModel

Also the view model doesn't contain a lot of logic:

public class SimpleLockableViewModel {
	private static final AtomicInteger userCounter = new AtomicInteger(0);
	private final String username = "User-" + userCounter.incrementAndGet();

	private final UiLockTracker<SimpleResource> lockTracker = new UiLockTracker<>(10, 5);
	private static SimpleResource sharedResource = new SimpleResource("the Resource Value");

	@Init
	public void init(@ContextParam(ContextType.DESKTOP) Desktop desktop) {
		desktop.enableServerPush(true);
		lockTracker.observe(new MvvmLockable<SimpleResource>(getUsername(), sharedResource) {
			@Override
			public void onLockEvent(LockEvent event) {
				super.onLockEvent(event);
				BindUtils.postNotifyChange(null, null, sharedResource, "value");
			}
		});
	}

	@Command
	public void lock() { lockTracker.lock(); }

	@Command
	public void unlock() { lockTracker.unlock(); }

	public UiLockable<SimpleResource> getLockable() { return lockTracker.getLockable(); }

	public String getUsername() { return username; }
}

Worth mentioning is line 10 that we enable server-push (LINK ME) as a prerequisite to allow for asynchronous UI updates.

Besides the usual @Command-bindings and getters, the only things remaining are the UiLockTracker (line 5) and UiLockable/MvvmLockable (line 11) classes.


Lockable/MvvmLockable

In order to "calculate" the lock status the UiLockable needs a resource, the current user (self) and the lock owner information. Owner changes are indicated with a LockEvent whenever onLockEvent() is called.

public class Lockable<T> {
	private final String self;
	private final T resource;
	private String owner;

	public UiLockable(String self, T resource) {
		this.self = self;
		this.resource = resource;
	}

	protected void onLockEvent(LockEvent event) {
		this.owner = event.getOwner();
	}

	public String getSelf() { return self; }

	public T getResource() { return resource; }

	public String getOwner() { return owner; }

	public LockStatus getStatus() {
		return owner == null ? LockStatus.AVAILABLE :
				self.equals(owner) ? LockStatus.OWNED : LockStatus.UNAVAILABLE;
	}

	protected Object getResourceKey() {
		... generates a unique key for the resource
	}
}

By overriding the onLockEvent method UI updates can be implemented (especially when using MVC).

When using MVVM the changing properties can be notified automatically as in the implementation below.

public class MvvmLockable<T> extends Lockable<T> {
	...
	@Override
	public void onLockEvent(LockEvent event) {
		super.onLockEvent(event);
		BindUtils.postNotifyChange(null, null, this, "owner");
		BindUtils.postNotifyChange(null, null, this, "status");
	}
	...

Additional notifications of vm properties are implemented by overriding the onLockEvent again.

	lockTracker.observe(new MvvmLockable<SimpleResource>(getUsername(), sharedResource) {
		@Override
		public void onLockEvent(LockEvent event) {
			super.onLockEvent(event);
			BindUtils.postNotifyChange(null, null, sharedResource, "value");
		}
	});

Notifying the sharedResource property will make sure the rendered resource value is also updated in all observing browsers after a lock event. This especially allows conditional notification for different requirements in your UI.

So far we haven't seen how those LockEvents are triggered and distributed among the observing clients. Let's get into the details.

UiLockTracker

The UiLockTracker is surely the core class of this example.

Summary

Example Sources

The code examples are available on github in the zk-template-examples repository

zul files: https://github.com/zkoss-demo/zk-template-examples/tree/master/src/main/webapp/locker
java classes: https://github.com/zkoss-demo/zk-template-examples/tree/master/src/main/java/zk/example/template/locker

Running the Example

Clone the repo

   git clone git@github.com:zkoss-demo/zk-template-examples.git

The example war file can be built using the gradle-wrapper (on windows simply omit the prefix './'):

   ./gradlew war

Execute using gretty:

   ./gradlew appRun

Execute using jetty-runner (faster startup):

   ./gradlew startJettyRunner

Then access the example http://localhost:8080/zk-template-examples/locker/


Comments



Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License.