Processing...
Description & Source Code
  • Description
  • View
    zk_calendar.zul
    calendar_editor.zul
  • Controller
    CalendarController.java
    QueueMessage.java
  • View Model
  • Model
    DemoCalendarData.java
    DemoCalendarEvent.java
    DemoCalendarModel.java

ZK Calendar is an Ajax component readily embeddable into any Java web application. In this demo, it's shown as a standalone, Google Calendar like application; offering functionalities such as filtering, detailed scheduling, multiple time zones, and daily/weekly/monthly view. Properties such as format and style can be easily configured by setting the calendar component's attributes. The main task for the developers is to handle the calendar events and preparing the event data model; similar to how other data components work in ZK.
Full Demo : ZK Calendar Demo
Doc : ZK Calendar Essentials

This feature requires ZK EE. It can also be used independently under commerical or GPL license.
zk_calendar.zul
<zk>
	<div apply="demo.app.zk_calendar.CalendarController">
		<hlayout valign="middle">
			<button id="today" label="Today" />
			<button id="prev" iconSclass="z-icon-arrow-left"/>
			<button id="next" iconSclass="z-icon-arrow-right"/>
			<separator width="50px" />
			<button id="pageDay" label="Day" width="60px" />
			<button id="pageWeek" label="Week" width="60px"/>
			<button id="pageMonth" label="Month" width="60px"/>
			<separator width="50px"/>
			Filter :
			<textbox id="filter"/>
			<button id="applyFilter" label="Apply"/>
			<button id="resetFilter" label="Reset"/>
		</hlayout>
		<separator bar="true" height="20px"/>
		<calendars id="calendars" firstDayOfWeek="Sunday" height="600px" timeZone="Main=GMT+0" mold="month"/>
	</div>

	<!-- Create/Update Event Popup -->
	<include src="/widgets/zk_calendar/zk_calendar/calendar_editor.zul" />
</zk>
calendar_editor.zul
<div viewModel="@id('vm') @init('demo.app.zk_calendar.CalendarEditorViewModel')" validationMessages="@id('vmsgs')">
	<window title="Create Calendar Event" border="normal" width="400px" 
		form="@id('fx') @load(vm.calendarEvent) @save(vm.calendarEvent, before='ok') @validator(vm.dateValidator)"
		allDay="@ref(vm.isAllDay(fx.beginDate,fx.endDate))"
		mode="popup" visible="@load(vm.visible)" position="center,center" >
		<vlayout hflex="1">
			<hlayout valign="middle">
				Lock this event : <checkbox checked="@bind(fx.locked)" />
				All Day: <checkbox checked="@load(allDay)" disabled="true" />
			</hlayout>
			<grid hflex='1'>
				<columns>
					<column width="100px" align="right" />
					<column />
				</columns>
				<rows>
					<row>
						BeginDate:
						<div>
							<datebox hflex="1" locale="en" timeZone="GMT+0"
								format="@load(allDay ? 'long' : 'long+medium')" 
								value="@bind(fx.beginDate)" errorMessage="@load(vmsgs.beginDate)" />
						</div>
					</row>
					<row>
						EndDate:
						<div>
							<datebox hflex="1" locale="en" timeZone="GMT+0"
								format="@load(allDay ? 'long' : 'long+medium')"
								value="@bind(fx.endDate)" errorMessage="@load(vmsgs.endDate)"/>
						</div>
					</row>
					<row>
						Color:
						<hlayout valign="middle">
							Border <colorbox value="@bind(fx.headerColor)" />
							Content <colorbox value="@bind(fx.contentColor)" />
						</hlayout>
		
					</row>
					<row>
						Title:
						<textbox multiline="true" rows="3" width="97%"
							value="@bind(fx.content)" />
					</row>
					<row>
						<cell colspan="2" style="text-align:center;">
							<hlayout>
								<button label="OK" onClick="@command('ok')" width="80px" />
								<button label="Cancel" onClick="@command('cancel')" width="80px" />
								<button label="Delete" onClick="@command('delete')" width="80px" />
							</hlayout>
						</cell>
					</row>
				</rows>
			</grid>
		</vlayout>
	</window>
</div>
CalendarController.java
package demo.app.zk_calendar;

import java.util.Calendar;
import java.util.TimeZone;

import org.zkoss.calendar.CalendarWebAppInit;
import org.zkoss.calendar.Calendars;
import org.zkoss.calendar.event.CalendarsEvent;
import org.zkoss.web.fn.ServletFns;
import org.zkoss.zk.ui.Component;
import org.zkoss.zk.ui.event.Event;
import org.zkoss.zk.ui.select.SelectorComposer;
import org.zkoss.zk.ui.select.annotation.Listen;
import org.zkoss.zk.ui.select.annotation.Wire;
import org.zkoss.zk.ui.sys.PageCtrl;
import org.zkoss.zkmax.ui.select.annotation.Subscribe;
import org.zkoss.zul.Style;
import org.zkoss.zul.Textbox;
import org.zkoss.zul.theme.Themes;

import javax.servlet.ServletException;


public class CalendarController extends SelectorComposer<Component> {

	private static final long serialVersionUID = 1L;

	@Wire
	private Calendars calendars;
	@Wire
	private Textbox filter;
	
	private DemoCalendarModel calendarModel;
	
	//the in editing calendar ui event
	private CalendarsEvent calendarsEvent = null;

	@Override
	public void doAfterCompose(Component comp) throws Exception {
		overrideCalendarTheme(comp);

		super.doAfterCompose(comp);
		calendarModel = new DemoCalendarModel(new DemoCalendarData().getCalendarEvents());
		calendars.setModel(this.calendarModel);
	}

	/**
	 * TODO: remove workaround once https://tracker.zkoss.org/browse/ZK-4771 is available and zk-calendar supports it
	 */
	private void overrideCalendarTheme(Component comp) throws ServletException {
		String uri = CalendarWebAppInit.getCalendarThemeURI(Themes.getCurrentTheme());
		new Style(uri).setPage(comp.getPage());
	}

	//control the calendar position
	@Listen("onClick = #today")
	public void gotoToday(){
		TimeZone timeZone = calendars.getDefaultTimeZone();
		calendars.setCurrentDate(Calendar.getInstance(timeZone).getTime());
	}
	@Listen("onClick = #next")
	public void gotoNext(){
		calendars.nextPage();
	}
	@Listen("onClick = #prev")
	public void gotoPrev(){
		calendars.previousPage();
	}
	
	//control page display
	@Listen("onClick = #pageDay")
	public void changeToDay(){
		calendars.setMold("default");
		calendars.setDays(1);
	}
	@Listen("onClick = #pageWeek")
	public void changeToWeek(){
		calendars.setMold("default");
		calendars.setDays(7);
	}
	@Listen("onClick = #pageMonth")
	public void changeToYear(){
		calendars.setMold("month");
	}
	
	//control the filter
	@Listen("onClick = #applyFilter")
	public void applyFilter(){
		calendarModel.setFilterText(filter.getValue());
		calendars.setModel(calendarModel);
	}
	@Listen("onClick = #resetFilter")
	public void resetFilter(){
		filter.setText("");
		calendarModel.setFilterText("");
		calendars.setModel(calendarModel);
	}

	//listen to the calendar-create and edit of a event data
	@Listen(CalendarsEvent.ON_ITEM_CREATE + " = #calendars; " +
			CalendarsEvent.ON_ITEM_EDIT + "  = #calendars")
	public void createEvent(CalendarsEvent event) {
		calendarsEvent = event;
		
		//to display a shadow when editing
		calendarsEvent.stopClearGhost();
		
		DemoCalendarEvent data = (DemoCalendarEvent)event.getCalendarItem();
		
		if(data == null) {
			data = new DemoCalendarEvent();
			data.setHeaderColor("#3366ff");
			data.setContentColor("#6699ff");
			data.setBeginDate(event.getBeginDate());
			data.setEndDate(event.getEndDate());
		} else {
			data = (DemoCalendarEvent) event.getCalendarItem();
		}
		//notify the editor
		QueueUtil.lookupQueue().publish(
				new QueueMessage(QueueMessage.Type.EDIT,data));
	}
	
	//listen to the calendar-update of event data, usually send when user drag the event data 
	@Listen(CalendarsEvent.ON_ITEM_UPDATE + " = #calendars")
	public void updateEvent(CalendarsEvent event) {
		DemoCalendarEvent data = (DemoCalendarEvent) event.getCalendarItem();
		data.setBeginDate(event.getBeginDate());
		data.setEndDate(event.getEndDate());
		calendarModel.update(data);
	}
	
	//listen to queue message from other controller
	@Subscribe(value = QueueUtil.QUEUE_NAME)
	public void handleQueueMessage(Event event) {
		if(!(event instanceof QueueMessage)) {
			return;
		} 
		QueueMessage message = (QueueMessage)event;
		switch(message.getType()){
		case DELETE:
			calendarModel.remove((DemoCalendarEvent)message.getData());
			//clear the shadow of the event after editing
			calendarsEvent.clearGhost(); 
			calendarsEvent = null;
			break;
		case OK:
			if (calendarModel.indexOf((DemoCalendarEvent)message.getData()) >= 0) {
				calendarModel.update((DemoCalendarEvent)message.getData());
			} else {
				calendarModel.add((DemoCalendarEvent)message.getData());
			}
		case CANCEL:
			//clear the shadow of the event after editing
			calendarsEvent.clearGhost();
			calendarsEvent = null;
			break;
		}
	}	
}
QueueMessage.java
package demo.app.zk_calendar;

import org.zkoss.zk.ui.event.Event;

public class QueueMessage extends Event {
	private static final long serialVersionUID = 1L;

	static public enum Type {
		EDIT, OK, DELETE, CANCEL;
	}
	
	private Type type;
	private Object data;
	
	public QueueMessage(Type type) {
		super("onCalendarMessage");
		this.type = type;
	}
	public QueueMessage(Type type, Object data) {
		this(type);
		this.data = data;
	}

	public Type getType() {
		return type;
	}

	public Object getData() {
		return data;
	}
}
CalendarEditorViewModel.java
package demo.app.zk_calendar;

import java.util.Date;
import java.util.Map;

import org.zkoss.bind.BindUtils;
import org.zkoss.bind.Property;
import org.zkoss.bind.ValidationContext;
import org.zkoss.bind.Validator;
import org.zkoss.bind.annotation.Command;
import org.zkoss.bind.annotation.Init;
import org.zkoss.bind.annotation.NotifyChange;
import org.zkoss.bind.validator.AbstractValidator;
import org.zkoss.zk.ui.event.EventListener;

public class CalendarEditorViewModel {
	
	private DemoCalendarEvent calendarEventData = new DemoCalendarEvent();
	
	private boolean visible = false;

	public DemoCalendarEvent getCalendarEvent() {
		return calendarEventData;
	}

	public boolean isVisible() {
		return visible;
	}

	public void setVisible(boolean visible) {
		this.visible = visible;
	}

	@Init
	public void init() {
		//subscribe a queue, listen to other controller
		QueueUtil.lookupQueue().subscribe(new QueueListener());
	}

	private void startEditing(DemoCalendarEvent calendarEventData) {
		this.calendarEventData = calendarEventData;
		visible = true;
		
		//reload entire view-model data when going to edit
		BindUtils.postNotifyChange(null, null, this, "*");
	}
	

	public boolean isAllDay(Date beginDate,Date endDate) {
		int M_IN_DAY = 1000 * 60 * 60 * 24; 
		boolean allDay = false;
		
		if(beginDate != null && endDate != null) {
			long between = endDate.getTime() - beginDate.getTime();
			allDay = between > M_IN_DAY;
		}
		return allDay;
	}
	
	public Validator getDateValidator(){
		return new AbstractValidator(){
			@Override
			public void validate(ValidationContext ctx) {
				Map<String,Property> formData = ctx.getProperties(ctx.getProperty().getValue());
				Date beginDate = (Date)formData.get("beginDate").getValue();
				Date endDate = (Date)formData.get("endDate").getValue();
				if(beginDate==null){
					addInvalidMessage(ctx, "beginDate","Begin date is empty");
				}
				if(endDate==null){
					addInvalidMessage(ctx, "endDate","End date is empty");
				}
				if(beginDate!=null && endDate!=null && beginDate.compareTo(endDate) >= 0){
					addInvalidMessage(ctx, "endDate","End date is before begin date");
				}
			}
		};
	}

	@Command
	@NotifyChange("visible")
	public void cancel() {
		QueueMessage message = new QueueMessage(QueueMessage.Type.CANCEL);
		QueueUtil.lookupQueue().publish(message);
		this.visible = false;
	}

	@Command
	@NotifyChange("visible")
	public void delete() {
		QueueMessage message = new QueueMessage(QueueMessage.Type.DELETE, calendarEventData);
		QueueUtil.lookupQueue().publish(message);
		this.visible = false;
	}

	@Command
	@NotifyChange("visible")
	public void ok() {
		QueueMessage message = new QueueMessage(QueueMessage.Type.OK, calendarEventData);
		QueueUtil.lookupQueue().publish(message);
		this.visible = false;
	}

	private class QueueListener implements EventListener<QueueMessage> {
		@Override
		public void onEvent(QueueMessage message)
				throws Exception {
			if (QueueMessage.Type.EDIT.equals(message.getType())){
				CalendarEditorViewModel.this.startEditing((DemoCalendarEvent)message.getData());
			}
		}
	}
}
DemoCalendarData.java
package demo.app.zk_calendar;

import java.text.ParseException;
import java.text.SimpleDateFormat;
import java.util.Calendar;
import java.util.Date;
import java.util.LinkedList;
import java.util.List;

import org.zkoss.calendar.api.CalendarItem;


public class DemoCalendarData {

	private List<CalendarItem> calendarEvents = new LinkedList<>();
	private final SimpleDateFormat DATA_FORMAT = new SimpleDateFormat("yyyy/MM/dd HH:mm");
	private Calendar cal = Calendar.getInstance();

	public DemoCalendarData() {
		init();
	}

	private void init() {
		int mod = cal.get(Calendar.MONTH) + 1;
		int year = cal.get(Calendar.YEAR);
		String date2 = mod > 9 ? year + "/" + mod + "" : year + "/" + "0" + mod;
		String date1 = --mod > 9 ? year + "/" + mod + "" : year + "/" + "0" + mod;
		++mod;
		String date3 = ++mod > 9 ? year + "/" + mod + "" : year + "/" + "0" + mod;
		// Red Events
		calendarEvents.add(new DemoCalendarEvent(getDate(date1 + "/28 00:00"), getDate(date1 + "/29 00:00"), "#A32929", "#D96666", "ZK Jet Released"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date1 + "/04 02:00"), getDate(date1 + "/05 03:00"), "#A32929", "#D96666", "Experience ZK SpreadSheet Live Demo!"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/21 05:00"), getDate(date2 + "/21 07:00"), "#A32929", "#D96666", "New Features of ZK Spreadsheet"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/08 00:00"), getDate(date2 + "/09 00:00"), "#A32929", "#D96666", "ZK Spreadsheet Released"));
		// Blue Events
		calendarEvents.add(new DemoCalendarEvent(getDate(date1 + "/29 03:00"), getDate(date2 + "/02 06:00"), "#3467CE", "#668CD9", "ZK Released"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/02 10:00"), getDate(date2 + "/02 12:30"), "#3467CE", "#668CD9", "New Feature of ZK "));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/17 14:00"), getDate(date2 + "/18 16:00"), "#3467CE", "#668CD9", "Case Study - Mecatena"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date3 + "/01 14:30"), getDate(date3 + "/01 17:30"), "#3467CE", "#668CD9", "ZK Unit Testing Project - zunit"));
		// Purple Events
		calendarEvents.add(new DemoCalendarEvent(getDate(date1 + "/29 08:00"), getDate(date2 + "/03 12:00"), "#7A367A", "#B373B3", "ZK Studio released"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/07 08:00"), getDate(date2 + "/07 12:00"), "#7A367A", "#B373B3", "Tutorial : Reading from the DB with Netbeans and ZK"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/13 11:00"), getDate(date2 + "/13 14:30"), "#7A367A", "#B373B3", "Small talk - ZK Charts"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/16 14:00"), getDate(date2 + "/18 16:00"), "#7A367A", "#B373B3", "Style Guide for ZK released !"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date3 + "/02 12:00"), getDate(date3 + "/02 17:00"), "#7A367A", "#B373B3", "Small talk -- Simple Database Access From ZK"));
		// Khaki Events
		calendarEvents.add(new DemoCalendarEvent(getDate(date1 + "/03 00:00"), getDate(date1 + "/04 00:00"), "#88880E", "#BFBF4D", "ZK UK User Group"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/13 05:00"), getDate(date2 + "/13 07:00"), "#88880E", "#BFBF4D", "How to Test ZK Application with Selenium"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/24 19:30"), getDate(date2 + "/24 20:00"), "#88880E", "#BFBF4D", "ZK Alfresco Talk"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date3 + "/03 00:00"), getDate(date3 + "/04 00:00"), "#88880E", "#BFBF4D", "ZK selected as SourceForge.net Project of the Month"));
		// Green Events
		calendarEvents.add(new DemoCalendarEvent(getDate(date1 + "/28 10:00"), getDate(date1 + "/28 12:30"), "#0D7813", "#4CB052", "ZK Mobile Released"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/03 00:00"), getDate(date2 + "/03 05:30"), "#0D7813", "#4CB052", "ZK Gmaps released"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/05 20:30"), getDate(date2 + "/06 00:00"), "#0D7813", "#4CB052", "Refresh with Five New ZK Themes!"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date2 + "/23 00:00"), getDate(date2 + "/25 16:30"), "#0D7813", "#4CB052", "ZK Roadmap Announced"));
		calendarEvents.add(new DemoCalendarEvent(getDate(date3 + "/01 08:30"), getDate(date3 + "/01 19:30"), "#0D7813", "#4CB052", "Build Database CRUD Application in 6 Steps"));
	}

	private Date getDate(String dateText) {
		try {
			return DATA_FORMAT.parse(dateText);
		} catch (ParseException e) {
			e.printStackTrace();
		}
		return null;
	}

	public List<CalendarItem> getCalendarEvents() {
		return calendarEvents;
	}
}
DemoCalendarEvent.java
package demo.app.zk_calendar;

import java.util.Date;

import org.zkoss.bind.annotation.Immutable;
import org.zkoss.calendar.impl.SimpleCalendarEvent;

public class DemoCalendarEvent extends SimpleCalendarEvent {
	private static final long serialVersionUID = 1L;

	public DemoCalendarEvent(Date beginDate, Date endDate, String headerColor, String contentColor, String content) {
		setHeaderColor(headerColor);
		setContentColor(contentColor);
		setContent(content);
		setBeginDate(beginDate);
		setEndDate(endDate);
	}

	public DemoCalendarEvent(Date beginDate, Date endDate, String headerColor, String contentColor, String content,
			String title) {
		setHeaderColor(headerColor);
		setContentColor(contentColor);
		setContent(content);
		setTitle(title);
		setBeginDate(beginDate);
		setEndDate(endDate);
	}

	public DemoCalendarEvent(Date beginDate, Date endDate, String headerColor, String contentColor, String content,
			String title, boolean locked) {
		setHeaderColor(headerColor);
		setContentColor(contentColor);
		setContent(content);
		setTitle(title);
		setBeginDate(beginDate);
		setEndDate(endDate);
		setLocked(locked);
	}
	
	public DemoCalendarEvent() {
		setHeaderColor("#FFFFFF");
		setContentColor("#000000");
	}
	
	@Override
	@Immutable
	public Date getBeginDate() {
		return super.getBeginDate();
	}

	@Override
	@Immutable
	public Date getEndDate() {
		return super.getEndDate();
	}
}
DemoCalendarModel.java
package demo.app.zk_calendar;

import java.util.Date;
import java.util.Iterator;
import java.util.LinkedList;
import java.util.List;

import org.zkoss.calendar.api.CalendarItem;
import org.zkoss.calendar.api.RenderContext;
import org.zkoss.calendar.impl.SimpleCalendarModel;

public class DemoCalendarModel extends SimpleCalendarModel {
	private static final long serialVersionUID = 1L;
	
	private String filterText = "";

	public DemoCalendarModel(List<CalendarItem> calendarEvents) {
		super(calendarEvents);
	}

	public void setFilterText(String filterText) {
		this.filterText = filterText;
	}

	@Override
	public List<CalendarItem> get(Date beginDate, Date endDate, RenderContext rc) {
		List<CalendarItem> list = new LinkedList<CalendarItem>();
		long begin = beginDate.getTime();
		long end = endDate.getTime();
				
		for (Iterator<?> itr = _list.iterator(); itr.hasNext();) {
			Object obj = itr.next();
			CalendarItem ce = obj instanceof CalendarItem ? (CalendarItem)obj : null;
			
			if(ce == null) break;
			
			long b = ce.getBeginDate().getTime();
			long e = ce.getEndDate().getTime();
			if (e >= begin && b < end && ce.getContent().toLowerCase().contains(filterText.toLowerCase()))
				list.add(ce);
		}
		return list;
	}

}