Use VueJS With ZK9 EmbeddedZK
James Chu, Potix Corporation
January 7, 2020
ZK 9.0.0
Overview
In the recently released ZK9, we provide a new way to integrate applications with ZK and front-end frameworks - Embedded ZK. For instance, VueJS is a progressive framework for building user interfaces. With the embedded ZK in ZK9, we could easily combine VueJS and ZK together. For more detail, we will demo how it works with modern front-end frameworks. We wil use ZK client side binding, embedded ZK, Bootstrap and VueJS in this demo project.
Demo
The new client side binding provides 4 methods - 2 at the client side, and 2 at the server side. Their relationships can be illustrated by the following diagram:
At client
We have to get the client binder first in order to use the client side methods. To get the binder, simply use
var binder = zkbind.$('$id');
in the scripts. After we have our client binder, we could use the command and after method to interact with the view model back to our server.
binder.command(commandName, data)
This method is used to trigger a command we have at server by giving the command name as the first parameter. The second parameter is a JavaScript object, which is used to pass any information you want with the command.
Note: You could also pass ZK widgets in the data object and use @BindingParam to get the corresponding ZK component at the server.
binder.after(commandName, callback)
This method is used to place a callback at the client after a command gets executed at the server.
At server
Here, we are going to introduce two new annotations at the server side for the new client side binding. They should be placed at the beginning of the class declaration of our View Model.
@NotifyCommand(value="commandName", onChange="_vm_.expression")
The notify command annotation allows us to trigger a command whenever the given expression changes at the server. Notice the command which gets triggered is a command in our view model. The _vm_ here means the current view model.
@ToClientCommand(commandNames)
The client command annotation allows us to put which commands we want to notify the client after execution has been done. Notice only the commands we put inside this annotation will trigger the callback we put in binder.after at client.
Demo
In this demo, we will use full calendar, which is a JavaScript calendar library under MIT license. We are going to wire up the full calendar with some ZK components back to the server with a View Model and a Data Model. The goal here is to bind the events coming out from full calendar to the commands we have in our View Model so that we can sync whatever changes in the calendar directly to our data model.
Implementation
We will have four main parts in this demo.
- zul page, which is the view of our demo
- js file, which will contain our event handlers for full calendar and our client side bindings.
- view model, where we handle all the commands that come from the client.
- data model, which act as a data accessing object to our data source.
zul page
Our zul page is relatively simple. It has three parts, a container for full calendar and two popups. The container will be just a native div element with an ID:
<n:div id="cal" />
The popups will be ZK popups and they will be used to create and modify events. The modify event popup looks like:
<popup id="modifyEventPop">
<window title="Modify Event: " width="500px">
<grid form="@id('fx') @load(vm.tempEvent) @save(vm.tempEvent, before='modEvent')">
<rows>
<row>
<label value="Event ID: " />
<label id="modId" value="@load(fx.id)" />
</row>
<row>
<label value="Title:" />
<textbox id="modTitle" value="@bind(fx.title)" />
</row>
<row>
<label value="Start Date:" />
<datebox id="modStart" value="@bind(fx.start)" format="long+medium" />
</row>
<row>
<label value="End Date:" />
<datebox id="modEnd" value="@bind(fx.end)" format="long+medium" />
</row>
</rows>
</grid>
<button id="modify" label="Modify"
onClick="@command('modEvent', pop=modifyEventPop)" />
<button label="Cancel" onClick="modifyEventPop.close()" />
</window>
</popup>
The popup shows when an eventClicked event is triggered. The data of the event will be pre-loaded before the popup shows. When the modify button gets clicked, a modEvent command will be fired back to our View Model, and we will update the modified event to our data model from there. The popup for creating event is very similar with the one above, we will omit its implementation here just to keep things simple.
js file
Our script is where the new client side binding begins to play a role at. The complete js file looks like:
zk.afterMount(function() {
var binder = zkbind.$('$cal'),
calConfig = {};
// day click handler
calConfig.dayClick = function(data, jsEvent, view) {
var popOffset = [jsEvent.clientX, jsEvent.clientY];
binder.command('doDayClicked', {dateClicked: data.toDate().getTime()})
.after(function() {
var newPop = zk.$('$newEventPop');
newPop.open(newPop, popOffset);
});
};
// event click handler
calConfig.eventClick = function(event, jsEvent, view) {
var popOffset = [jsEvent.clientX, jsEvent.clientY];
binder.command('doEventClicked', {evtId: event.id})
.after(function() {
var modPop = zk.$('$modifyEventPop');
modPop.open(modPop, popOffset);
});
}
// event drop handler and event resize handler
calConfig.eventResize = calConfig.eventDrop =
function(event, delta, revertFunc, jsEvent, ui, view) {
var startTime = event.start ?
event.start.toDate().getTime() : 0,
endTime = event.end ? event.end.toDate().getTime() : 0;
binder.command('doEventChanged', {evtId: event.id, startTime: startTime, endTime: endTime});
}
$('#cal').fullCalendar(calConfig);
// the event handler of after 'doCommandChange' from server
binder.after('doEventsChange', function(events) {
$('#cal').fullCalendar('removeEvents');
$('#cal').fullCalendar('addEventSource', events);
$('#cal').fullCalendar('rerenderEvents');
});
});
The first thing we do here at line 3 is to get our client binder. Then in our day clicked event handler, we use binder.command to trigger the doDayClicked command and pass the clicked date back to our view model at line 10. At line 11, we use binder.after to open our popup. Notice that when cascading binder.command and binder.after, the first argument in binder.after can be omitted. eventClick, eventDrop, and eventResize handlers follow the similar concept as well. Line 37 is where we initialize our full calendar and finally, begins at line 40 is where our doEventsChange callback. We use this callback to ensure that every time when events change at the View Model, they will be updated in our view.
View Model
Our view model is where we put all of our commands at. The structure of our view model looks like:
@NotifyCommand(value="doEventsChange", onChange="_vm_.events")
@ToClientCommand({"doEventClicked", "doDayClicked", "doEventsChange"})
public class DemoViewModel {
private EventsDataModel dataModel;
private Collection<EventObject> events;
private EventObject tempEvent;
@Init
public void init() throws GeneralSecurityException, IOException {
// init event data model
dataModel = new DemoDataModel();
events = dataModel.getEvents();
}
@Command
@NotifyChange("tempEvent")
public void doDayClicked(@BindingParam("dateClicked") long dateClicked);
@Command
@NotifyChange("tempEvent")
public void doEventClicked(@BindingParam("evtId") String evtId);
@Command
@NotifyChange("events")
public void doEventChanged(@BindingParam("evtId") String evtId,
@BindingParam("startTime") long startTime, @BindingParam("endTime") long endTime);
@Command
@NotifyChange("events")
public void modEvent(@BindingParam("pop") Popup pop);
@Command
@NotifyChange("events")
public void createEvent(@BindingParam("pop") Popup pop);
}
Notice we omit command implementations here just to focus on the new client side binding. First we put our @NotifyCommand and @ToClientCommand on top of our class declaration.
At line 1,
@NotifyCommand(value="doEventsChange", onChange="_vm_.events")
We specify that our view model will trigger the doEventsChange command whenever events are changed. The _vm_ here stands for the current view model.
At line 2,
@ToClientCommand({"doEventClicked", "doDayClicked", "doEventsChange"})
We specify that every time these commands execute, ZK will notify our client, and if there is a binder.after callback at client, it will be invoked. Notice that we do not have a doEventsChange command in our view model, we put @NotifyCommand here just because we want to trigger the callback function at the client.
data model
Data model is used to assess our data source. Here's the class diagram of our data model:
Since we can have different data sources, our data model will have different implementations depending on the data source. As shown in the demo above, we use a mock data source, which is just a map object in memory. If you check out the source code of this demo, we also have a implementation with Google Calendar's API.
Download
You can download the war file and all of the source code for this demo in Github
Comments
Copyright © Potix Corporation. This article is licensed under GNU Free Documentation License. |