Features
Spring Boot (2.5.0) application that displays a web page with JavaScript Scheduler component from DayPilot Pro for JavaScript.
The Scheduler displays events for multiple resources.
The Scheduler loads event and resource data from a REST/JSON endpoint.
Basic drag and drop operations supported (creating, moving and resizing events).
Custom event rendering (event bar color is stored in the database, configurable using a context menu)
License
Licensed for testing and evaluation purposes. Please see the license agreement included in the sample project. You can use the source code of the tutorial if you are a licensed user of DayPilot Pro for JavaScript.
Basic Scheduler Configuration (HTML5)
Our web application will display a single static HTML page. In order to display the DayPilot Scheduler component we need to add the following code to the HTML:
<!-- placeholder div -->
<div id="dp"></div>
<!-- DayPilot library -->
<script src="js/daypilot/daypilot-all.min.js"></script>
<!-- Scheduler initialization -->
<script>
const dp = new DayPilot.Scheduler("dp");
dp.startDate = DayPilot.Date.today().firstDayOfMonth();
dp.days = DayPilot.Date.today().daysInMonth();
dp.scale = "Day";
dp.timeHeaders = [
{groupBy: "Month"},
{groupBy: "Day", format: "d"}
];
dp.init();
</script>
We have added a couple of configuration options to the Scheduler initialization code - this config will display the current month on the time axis (one cell per day).
Loading Rows from Database
The Scheduler rows (resources) are defined in the database and we will load them using an HTTP call:
dp.rows.load("/api/resources");
This method loads the resources from the server using a GET request and automatically updates the Scheduler when the response arrives.
Sample JSON response:
[
{"name":"Resource 1","id":1},
{"name":"Resource 2","id":2},
{"name":"Resource 3","id":3},
{"name":"Resource 4","id":4},
{"name":"Resource 5","id":5},
{"name":"Resource 6","id":6},
{"name":"Resource 7","id":7},
{"name":"Resource 8","id":8},
{"name":"Resource 9","id":9}
]
The /api/resources
endpoint is implemented using a standard Spring MVC controller:
package org.daypilot.demo.html5schedulerspring.controller;
// ...
@RestController
public class MainController {
@Autowired
ResourceRepository rr;
@RequestMapping("/api/resources")
Iterable<Resource> resources() {
return rr.findAll();
}
// ...
}
Loading Scheduler Events (Appointments, Reservations)
There is a similar method for loading events:
dp.events.load("/api/events");
It calls the endpoint at the specified URL to get the event data. The Scheduler automatically appends from
and to
parameters the to URL query string. They pass the start and end of the current schedule to the Spring controller. In November 2021 the request URL will look like this:
http://localhost:8081/api/events?start=2021-11-01T00:00:00&end=2021-12-01T00:00:00
This will help us on the server side to select only the relevant events from the database. The endpoint is implemented in the MainController.java
file:
package org.daypilot.demo.html5schedulerspring.controller;
// ...
@RestController
public class MainController {
@Autowired
EventRepository er;
@GetMapping("/api/events")
@JsonSerialize(using = LocalDateTimeSerializer.class)
Iterable<Event> events(@RequestParam("start") @DateTimeFormat(iso=ISO.DATE_TIME) LocalDateTime start, @RequestParam("end") @DateTimeFormat(iso=ISO.DATE_TIME) LocalDateTime end) {
return er.findBetween(start, end);
}
// ...
}
Sample JSON response:
[
{"id":1,"text":"Reservation","start":"2021-11-03T00:00:00","end":"2021-11-07T00:00:00","color":null,"resource":2}
]
Creating New Events using Drag and Drop
The Scheduler supports drag and drop for creating new events. Whenever a user selects a time range, onTimeRangeSelected
event is fired.
We will add an event handler that asks for event name using a quick modal dialog - and save the new event to the database using an HTTP call.
The modal dialog is created using DayPilot.Modal.form() that lets you build a modal form dynamically (see also Modal Builder - an online application you can use to design the modal dialog visually). This example uses a simple modal dialog with a single field (“Event name”).
<script>
const dp = new DayPilot.Scheduler("dp");
// ...
dp.onTimeRangeSelected = async args => {
const form = [
{
name: "Event name",
id: "name"
}
];
const data = {
name: "Event"
};
const modal = await DayPilot.Modal.form(form, data);
dp.clearSelection();
if (modal.canceled) {
return;
}
const params = {
start: args.start,
end: args.end,
text: modal.result.name,
resource: args.resource
};
const {data: result} = await DayPilot.Http.post("/api/events/create", params);
dp.events.add(result);
dp.message("Event created");
};
dp.init();
</script>
The Spring backend saves the event to the database and returns the event data (including a generated ID):
package org.daypilot.demo.html5schedulerspring.controller;
// ...
@RestController
public class MainController {
@Autowired
EventRepository er;
// ...
@PostMapping("/api/events/create")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Transactional
Event createEvent(@RequestBody EventCreateParams params) {
Resource r = rr.findById(params.resource).get();
Event e = new Event();
e.setStart(params.start);
e.setEnd(params.end);
e.setText(params.text);
e.setResource(r);
er.save(e);
return e;
}
public static class EventCreateParams {
public LocalDateTime start;
public LocalDateTime end;
public String text;
public Long resource;
}
}
Custom Event Colors
Now, we let users change the color of the event using a context menu.
First, we define the context menu using contextMenu
property:
<script>
const dp = new DayPilot.Scheduler("dp");
// ...
dp.contextMenu = new DayPilot.Menu({
items: [
{
text: "Blue",
icon: "icon icon-blue",
color: "#1155cc",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
{
text: "Green",
icon: "icon icon-green",
color: "#6aa84f",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
{
text: "Yellow",
icon: "icon icon-yellow",
color: "#f1c232",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
{
text: "Red",
icon: "icon icon-red",
color: "#cc0000",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
]
});
dp.init();
</script>
By default, the context menu opens on right click. However, it may be hard for new users to guess that this option is available. Fortunately, there is a built-in tool to add a hover context menu hint - an active area. The active area will be displayed at the specified position (in the upper-right corner of the event) when a user hovers over the event box. We set the active area action to "ContextMenu"
- this will open the default event context menu on click.
<script>
const dp = new DayPilot.Scheduler("dp");
// ...
dp.onBeforeEventRender = args => {
args.data.areas = [
{
top: 10,
right: 4,
width: 16,
height: 16,
symbol: "icons/daypilot.svg#minichevron-down-4",
fontColor: "#999",
visibility: "Hover",
action: "ContextMenu",
style: "background-color: #f9f9f9; border: 1px solid #ccc; cursor:pointer;"
}
];
};
dp.init();
</script>
Our active area now looks like this:
The context menu items set the event color using an AJAX call to /api/events/setColor
:
async function updateColor(e, color) {
const params = {
id: e.id(),
color: color
};
await DayPilot.Http.post("/api/events/setColor", params);
e.data.color = color;
dp.events.update(e);
dp.message("Color updated");
}
The Spring MVC controller that defines the API endpoint:
package org.daypilot.demo.html5schedulerspring.controller;
// ...
@RestController
public class MainController {
@Autowired
EventRepository er;
// ...
@PostMapping("/api/events/setColor")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Transactional
Event setColor(@RequestBody SetColorParams params) {
Event e = er.findById(params.id).get();
e.setColor(params.color);
er.save(e);
return e;
}
// ...
public static class SetColorParams {
public Long id;
public String color;
}
}
The last task is to use the apply the saved color to the events during Scheduler rendering. The color is available in the event data JSON array as "color" property:
[
{"id":1,"text":"Reservation","start":"2021-11-03T00:00:00","end":"2021-11-07T00:00:00","color":"#6aa84f","resource":2}
]
We need to map it to the barColor property of the event data object:
dp.onBeforeEventRender = (args) => {
args.data.barColor = args.data.color;
// ...
};
See also event customization [doc.daypilot.org].
Complete HTML Page
src/main/resources/static/index.html
<!DOCTYPE html>
<html>
<head>
<meta charset="utf-8"/>
<title>Using JavaScript/HTML5 Scheduler in Spring Boot (Java)</title>
<link href="css/main.css" rel="stylesheet" type="text/css">
</head>
<body>
<div class="header">
<h1><a href='https://code.daypilot.org/70997/using-javascript-html5-scheduler-in-spring-boot-java'>Using
JavaScript/HTML5 Scheduler in Spring Boot (Java)</a></h1>
<div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - HTML5 Calendar/Scheduling Components
for JavaScript/Angular/React/Vue
</div>
</div>
<div class="main">
<div id="dp"></div>
</div>
<!-- DayPilot library -->
<script src="js/daypilot/daypilot-all.min.js"></script>
<script>
const dp = new DayPilot.Scheduler("dp");
dp.startDate = DayPilot.Date.today().firstDayOfMonth();
dp.days = DayPilot.Date.today().daysInMonth();
dp.scale = "Day";
dp.timeHeaders = [
{groupBy: "Month"},
{groupBy: "Day", format: "d"}
];
dp.onTimeRangeSelected = async args => {
const form = [
{
name: "Event name",
id: "name"
}
];
const data = {
name: "Event"
};
const modal = await DayPilot.Modal.form(form, data);
dp.clearSelection();
if (modal.canceled) {
return;
}
const params = {
start: args.start,
end: args.end,
text: modal.result.name,
resource: args.resource
};
const {data: result} = await DayPilot.Http.post("/api/events/create", params);
dp.events.add(result);
dp.message("Event created");
};
dp.onEventMove = async args => {
const params = {
id: args.e.id(),
start: args.newStart,
end: args.newEnd,
resource: args.newResource
};
await DayPilot.Http.post("/api/events/move", params);
dp.message("Event moved");
};
dp.onEventResize = async args => {
const params = {
id: args.e.id(),
start: args.newStart,
end: args.newEnd,
resource: args.e.resource()
};
await DayPilot.Http.post("/api/events/move", params);
dp.message("Event resized");
};
dp.onBeforeEventRender = args => {
args.data.barColor = args.data.color;
args.data.areas = [
{
top: 10,
right: 4,
width: 16,
height: 16,
symbol: "icons/daypilot.svg#minichevron-down-4",
fontColor: "#999",
visibility: "Hover",
action: "ContextMenu",
style: "background-color: #f9f9f9; border: 1px solid #ccc; cursor:pointer;"
}
];
};
dp.contextMenu = new DayPilot.Menu({
items: [
{
text: "Blue",
icon: "icon icon-blue",
color: "#1155cc",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
{
text: "Green",
icon: "icon icon-green",
color: "#6aa84f",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
{
text: "Yellow",
icon: "icon icon-yellow",
color: "#f1c232",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
{
text: "Red",
icon: "icon icon-red",
color: "#cc0000",
onClick: (args) => {
updateColor(args.source, args.item.color);
}
},
]
});
dp.init();
dp.rows.load("/api/resources");
dp.events.load("/api/events");
async function updateColor(e, color) {
const params = {
id: e.id(),
color: color
};
await DayPilot.Http.post("/api/events/setColor", params);
e.data.color = color;
dp.events.update(e);
dp.message("Color updated");
}
</script>
</body>
</html>
Complete Spring MVC Controller
MainController.java
package org.daypilot.demo.html5schedulerspring.controller;
import java.time.LocalDateTime;
import javax.transaction.Transactional;
import org.daypilot.demo.html5schedulerspring.domain.Event;
import org.daypilot.demo.html5schedulerspring.domain.Resource;
import org.daypilot.demo.html5schedulerspring.repository.EventRepository;
import org.daypilot.demo.html5schedulerspring.repository.ResourceRepository;
import org.springframework.beans.factory.annotation.Autowired;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
import org.springframework.web.bind.annotation.GetMapping;
import org.springframework.web.bind.annotation.PostMapping;
import org.springframework.web.bind.annotation.RequestBody;
import org.springframework.web.bind.annotation.RequestMapping;
import org.springframework.web.bind.annotation.RequestParam;
import org.springframework.web.bind.annotation.ResponseBody;
import org.springframework.web.bind.annotation.RestController;
import com.fasterxml.jackson.databind.annotation.JsonSerialize;
import com.fasterxml.jackson.datatype.jsr310.ser.LocalDateTimeSerializer;
@RestController
public class MainController {
@Autowired
EventRepository er;
@Autowired
ResourceRepository rr;
@RequestMapping("/api")
@ResponseBody
String home() {
return "Welcome!";
}
@RequestMapping("/api/resources")
Iterable<Resource> resources() {
return rr.findAll();
}
@GetMapping("/api/events")
@JsonSerialize(using = LocalDateTimeSerializer.class)
Iterable<Event> events(@RequestParam("start") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime start, @RequestParam("end") @DateTimeFormat(iso = ISO.DATE_TIME) LocalDateTime end) {
return er.findBetween(start, end);
}
@PostMapping("/api/events/create")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Transactional
Event createEvent(@RequestBody EventCreateParams params) {
Resource r = rr.findById(params.resource).get();
Event e = new Event();
e.setStart(params.start);
e.setEnd(params.end);
e.setText(params.text);
e.setResource(r);
er.save(e);
return e;
}
@PostMapping("/api/events/move")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Transactional
Event moveEvent(@RequestBody EventMoveParams params) {
Event e = er.findById(params.id).get();
Resource r = rr.findById(params.resource).get();
e.setStart(params.start);
e.setEnd(params.end);
e.setResource(r);
er.save(e);
return e;
}
@PostMapping("/api/events/setColor")
@JsonSerialize(using = LocalDateTimeSerializer.class)
@Transactional
Event setColor(@RequestBody SetColorParams params) {
Event e = er.findById(params.id).get();
e.setColor(params.color);
er.save(e);
return e;
}
public static class EventCreateParams {
public LocalDateTime start;
public LocalDateTime end;
public String text;
public Long resource;
}
public static class EventMoveParams {
public Long id;
public LocalDateTime start;
public LocalDateTime end;
public Long resource;
}
public static class SetColorParams {
public Long id;
public String color;
}
}
Data Layer (Domain Classes)
Event.java
package org.daypilot.demo.html5schedulerspring.domain;
import java.time.LocalDateTime;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
import javax.persistence.ManyToOne;
import com.fasterxml.jackson.annotation.JsonIgnore;
import com.fasterxml.jackson.annotation.JsonProperty;
@Entity
public class Event {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
Long id;
String text;
LocalDateTime start;
LocalDateTime end;
@ManyToOne
@JsonIgnore
Resource resource;
String color;
@JsonProperty("resource")
public Long getResourceId() {
return resource.getId();
}
public Long getId() {
return id;
}
public void setId(Long id) {
this.id = id;
}
public String getText() {
return text;
}
public void setText(String text) {
this.text = text;
}
public LocalDateTime getStart() {
return start;
}
public void setStart(LocalDateTime start) {
this.start = start;
}
public LocalDateTime getEnd() {
return end;
}
public void setEnd(LocalDateTime end) {
this.end = end;
}
public Resource getResource() {
return resource;
}
public void setResource(Resource resource) {
this.resource = resource;
}
public String getColor() { return color; }
public void setColor(String color) { this.color = color; }
}
Resource.java
package org.daypilot.demo.html5schedulerspring.domain;
import javax.persistence.Entity;
import javax.persistence.GeneratedValue;
import javax.persistence.GenerationType;
import javax.persistence.Id;
@Entity
public class Resource {
@Id
@GeneratedValue(strategy=GenerationType.IDENTITY)
Long Id;
String name;
public Long getId() {
return Id;
}
public void setId(Long id) {
this.Id = id;
}
public String getName() {
return name;
}
public void setName(String name) {
this.name = name;
}
}
Data Access Layer (Repository Classes)
EventRepository.java
package org.daypilot.demo.html5schedulerspring.repository;
import java.time.LocalDateTime;
import java.util.List;
import org.daypilot.demo.html5schedulerspring.domain.Event;
import org.springframework.data.jpa.repository.Query;
import org.springframework.data.repository.CrudRepository;
import org.springframework.data.repository.query.Param;
import org.springframework.format.annotation.DateTimeFormat;
import org.springframework.format.annotation.DateTimeFormat.ISO;
public interface EventRepository extends CrudRepository<Event, Long> {
@Query("from Event e where not(e.end < :from and e.start > :to)")
public List<Event> findBetween(@Param("from") LocalDateTime start, @Param("to") @DateTimeFormat(iso=ISO.DATE_TIME) LocalDateTime end);
}
ResourceRepository.java
package org.daypilot.demo.html5schedulerspring.repository;
import org.daypilot.demo.html5schedulerspring.domain.Resource;
import org.springframework.data.repository.CrudRepository;
public interface ResourceRepository extends CrudRepository<Resource, Long> {
}
Maven Project File
pom.xml
<project xmlns="http://maven.apache.org/POM/4.0.0" xmlns:xsi="http://www.w3.org/2001/XMLSchema-instance"
xsi:schemaLocation="http://maven.apache.org/POM/4.0.0 http://maven.apache.org/xsd/maven-4.0.0.xsd">
<modelVersion>4.0.0</modelVersion>
<parent>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-parent</artifactId>
<version>2.4.6</version>
<relativePath/>
</parent>
<groupId>org.daypilot.demo</groupId>
<artifactId>html5-scheduler-spring</artifactId>
<version>0.0.1-SNAPSHOT</version>
<properties>
<java.version>1.8</java.version>
</properties>
<dependencies>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-web</artifactId>
</dependency>
<dependency>
<groupId>com.fasterxml.jackson.datatype</groupId>
<artifactId>jackson-datatype-jsr310</artifactId>
</dependency>
<dependency>
<groupId>com.h2database</groupId>
<artifactId>h2</artifactId>
<scope>runtime</scope>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-data-jpa</artifactId>
</dependency>
<dependency>
<groupId>org.springframework.boot</groupId>
<artifactId>spring-boot-starter-security</artifactId>
</dependency>
</dependencies>
</project>