From Documentation

Jump to: navigation, search




  • Author
    Han Hsu, Potix Corporation
  • Date
    April 2, 2015
  • Version
    ZK 8.0.0.FL.20150331

Contents

Introduction

In a recent blog, we have introduced ZK 8's new client side binding and showed how we can use it to make a Polymer component work with ZK. In this smalltalk, we will have a more complete discussion of what will be included in the new client side binding up to date and how we can use these new methods to interact with a more complex client side library. In the second part of this post, we will present a simple demo along with its implementation to show you how to actually work with the new client side binding in real projects.

ZK 8's client side binding

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:


ZK8 Client Binding Chart01.jpg

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:


Fullcalendar datamodel.png

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.