Introduction

The Fragment component is one of the highlights in the upcoming ZK 8.5. It has an experimental offline recovery feature that let developers keep the user input data offline and handle the data online. It will be helpful if you are building a Progressive Web App (PWA) with ZK.

Progressive Web App (PWA)

Progressive Web App (PWA) uses the latest web technologies to enhance your web applications, making it more like native mobile apps. With the help of the manifest file, users can create shortcuts to the webapps on the home screen and execute them just like native apps. With Service workers you can cache the essential data to the browser to decrease page loading time and enable the offline use. For more information, please refer to this page: Progressive Web Apps.

Demo

With the aid of Service workers, the page can still display data and Fragment component can do some interaction even if users have no access to the Internet. When users access the Internet again, the preserved data will be uploaded to server to ensure a seamless offline-online experience.

On PC.

On mobile phone, users can get a better experience with a splash screen and an immersive full screen.

Required Configuration

Versions

Example

Service Worker

Add zul/zhtml and their dependencies to the cache list.

var CACHE_NAME = 'my-site-cache-v1';
var urlsToCache = [
	'/zkfragment-pwa-demo/',
	'/zkfragment-pwa-demo/?home=1',
	'/zkfragment-pwa-demo/index.html',
	'/zkfragment-pwa-demo/demo.zul',
	'/zkfragment-pwa-demo/zkau/web/js/zk.wpd',
	'/zkfragment-pwa-demo/zkau/web/js/zul.lang.wpd',
	'/zkfragment-pwa-demo/zkau/web/js/zkbind.wpd',
	'/zkfragment-pwa-demo/zkau/web/zul/css/zk.wcs',
	'/zkfragment-pwa-demo/zkau/web/js/zkmax.wgt.wpd',
	'/zkfragment-pwa-demo/zkau/web/js/zul.utl.wpd',
	'/zkfragment-pwa-demo/zkau/web/js/zk.fmt.wpd',
	'/zkfragment-pwa-demo/css/FragmentDemo.css',
	'/zkfragment-pwa-demo/css/offline-theme-default-indicator.css',
	'/zkfragment-pwa-demo/static/bundle.js',
	'/zkfragment-pwa-demo/static/offline.min.js'
];

self.addEventListener('install', function (event) {
	event.waitUntil(
		caches.open(CACHE_NAME).then(function(cache) {
			return cache.addAll(urlsToCache);
		})
	);
});
  • Line 7-17: Cache the js and css for offline use. You can see all the dependencies in the page HTML source.

We recommend you to apply the Network falling back to cache/Network or cache caching strategy for better compatibility since ZK is server-side rendering.

self.addEventListener('fetch', function (event) {
	console.log('fetch', event.request.url);
	var res = event.request;
	// omitted
	if (/\.(zul|zhtml)$/.test(res.url)) {
		event.respondWith(
			fetch(res).catch(function() {
				return caches.match(res);
			})
		);
		return;
	}
	// omitted
});
  • Line 5: Apply “network falling back to cache” for zul/zhtml.

ZUML

You need to assign an id and a recover id for Fragment component in order to activate the offline recovery feature. Then bind a command on the onRecover event and pass event.vm (the vm object) and event.packet to the command for later use.

<fragment id="fg" recoverId="${vm.recoverId}"
	  exps="@bind(vm.expenses)" toggleEdit="@bind(vm.toggleEdit)"
	  newExp="@bind(vm.newExpense)" editExp="@bind(vm.editExpense)"
	  total="@bind(vm.totalExpenses)" 
	  onRecover="@command('recover', vm=event.vm, packet=event.packet)"><![CDATA[
    // omitted
]]></fragment>
  • Line 1: Assign id property and recoverId property.
  • Line 5: Listen to the onRecover event that will trigger a command.

If you want to enhance the offline experience;  that is, users can still do some interaction without Internet connection, you can write the offline logic in <script>. Fragment widget provides some API to help.

  • onOffline(annotation, func):
    Handles annotation actions when offline. For instance, onOffline('command') handles @command and executes the provided function.We provided these annotations for now.

    Annotation Description
    save Handles @save. Typically the data of Fragment component is updated by the server responses. In offline you can do save data manually.
    command Handles @command/@global-command. You can assign what could be executed instead in offline.
    beforeRecover A special annotation which indicates the timing after the connection is up again and before the Fragment component will do the recover action.
  • save(key, val):
    Saves values of the desired property.
  • load(key):
    Loads the desired property.
<script>
zk.afterMount(function () {
 	// omitted
 	fg.onOffline('command', function (name, args) {
		// console.log('[@command]', name, args);
		if (name == 'toggleEditMethod') {
			var exps = fg.load('exps');
			fg.save('editExp', exps[args.id]);
			fg.save('toggleEdit', true);
			return;
		}
 	 	// omitted
 	});
	fg.onOffline('save', function (key, val) {
		// console.log('[@save]', key, val);
		fg.save(key, val);
	});
	fg.onOffline('beforeRecover', function (packet) {
		packet['exps'] = fg.load('exps');
		packet['newExp'] = fg.load('newExp');
		packet['editExp'] = fg.load('editExp');
		packet['toggleEdit'] = fg.load('toggleEdit');
	});
});
</script>
  • Line 4: Handles @command and @global-command when offline. Use load and save manually.
  • Line 14: Handles @save when offline. The Fragment widget provides save(key, val) API.
  • Line 18: The beforeRecover is a special annotation that let developers collect the data into packet for recovery use when the connection is up again and before the Fragment component does the recover action.

ViewModel

Once the onRecover event is triggered, you will receive an OfflineRecoverEvent object, which contains a vm object and a packet map. The vm object stores the data users input when offline while the packet map includes data which developers filled in during onOffline (‘beforeRecover’). You can leverage the data based on your need. Note that the data is not verified.

public class FragmentDemoVM {
	// omitted

	@Command
	@NotifyChange("*")
	public void recover(@BindingParam("vm") FragmentDemoVM vm,
	                    @BindingParam("packet") Map<String, Object> packet) {
		// You decide how to use this data.
		// For example, update the data of this ViewModel 
		// so users can make changes even in offline
		Expense oldExp = vm.getNewExpense();
		newExpense.setAmount(oldExp.getAmount());
		newExpense.setCategory(oldExp.getCategory());
		// omitted
		if (!packet.isEmpty()) {
			toggleEdit = packet.containsKey("toggleEdit") && (boolean) packet.get("toggleEdit");
			// omitted
		}
	}
}
  • Line 5: Add @NotifyChange if you change the property
  • Line 6: Retrieve the vm object.

Downloads

The whole demo project can be found on Github project.

If you enjoyed this post, please consider leaving a comment or subscribing to the RSS feed to have future articles delivered to your feed reader.

Leave a Reply