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.