Features

  • Spring Boot (3.3.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)

html5 javascript scheduler spring boot java initialization

Our web application will display a single static HTML page (index.html). 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", {
      startDate: DayPilot.Date.today().firstDayOfMonth(),
      days: DayPilot.Date.today().daysInMonth(),
      scale: "Day",
      timeHeaders: [
        {groupBy: "Month"},
        {groupBy: "Day", format: "d"}
      ]
    });
    dp.init();

</script>

We have added a couple of configuration options to the Scheduler initialization code:

Loading Rows from Database

html5 javascript scheduler spring boot java rows

The Scheduler rows (resources) are defined in the database. We e will load them using the rows.load() method from a JSON backend endpoint:

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.

The structure of the resource data is described in the resources property API documentation.

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)

html5 javascript scheduler spring boot java reservations

To load and display events in the Scheduler, you can use the events.load() method:

dp.events.load("/api/events");

The events.load() method 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. This way, the start and end of the current schedule is passed to the Spring controller.

In November 2024 the request URL will look like this:

http://localhost:8081/api/events?start=2024-11-01T00:00:00&end=2024-12-01T00:00:00

These query string parameters will help us side to select only the relevant events from the database.

The /api/events 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":"2024-11-03T00:00:00","end":"2024-11-07T00:00:00","color":null,"resource":2}
]

Creating New Events using Drag and Drop

html5 javascript scheduler spring boot java event creation

The Scheduler supports drag and drop for creation of new events.

  • Whenever a user selects a time range, the onTimeRangeSelected event is fired.

  • We will add an event handler that asks for an event name using a simple modal dialog.

  • After the user confirms the new name, the app creates a new event in the database using an HTTP call to /api/events/create.

The modal dialog is created using DayPilot.Modal.form(). It 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", {
    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

html5 javascript scheduler spring boot java event colors

Now, we will let users change the color of the event using a context menu.

First, we define a new context menu using the contextMenu property:

<script>
  const dp = new DayPilot.Scheduler("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", {

    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:

html5 javascript scheduler spring boot java event active area

The context menu items set the event color using an HTTP 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;
    }


}

html5 javascript scheduler spring boot java event bar

The last task is to apply the saved color to the event during Scheduler rendering. The color is available in the event data JSON array, in the color property:

[
  {"id":1,"text":"Reservation","start":"2024-11-03T00:00:00","end":"2024-11-07T00:00:00","color":"#6aa84f","resource":2}
]

We need to map it to the barColor property of the event data object. This can be done using the onBeforeEventRender event.

onBeforeEventRender = (args) => {
    
    args.data.barColor = args.data.color;
    
    // ...

}

See also event customization [doc.daypilot.org].

Complete HTML Source

This is the full source code of the HTML page that displays the Scheduler component (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>

<style>
    .area-menu {
        border: 1px solid #999;
        color: #999;
        border-radius: 10px;
        cursor:pointer;
    }
    .area-menu:hover {
        border: 1px solid #666;
        color: #666;
    }
</style>

<script>
  const dp = new DayPilot.Scheduler("dp", {
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    days: DayPilot.Date.today().daysInMonth(),
    scale: "Day",
    timeHeaders: [
      {groupBy: "Month"},
      {groupBy: "Day", format: "d"}
    ],
    eventHeight: 40,
    durationBarHeight: 6,
    rowHeaderColumns: [
      {text: "Name"},
      {text: "Type", display: "type"}
    ],
    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");
    },
    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");
    },
    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");
    },
    onBeforeEventRender: args => {
      const defaultColor = "#3F85A4";
      const color = args.data.color || defaultColor;
      args.data.barColor = color;
      args.data.areas = [
        {
          top: 12,
          right: 6,
          width: 18,
          height: 18,
          symbol: "icons/daypilot.svg#minichevron-down-4",
          backColor: "#f9f9f9",
          visibility: "Visible",
          action: "ContextMenu",
          cssClass: "area-menu",
        }
      ];
    },
    contextMenu: new DayPilot.Menu({
      items: [
        {
          text: "Blue",
          icon: "icon icon-blue",
          color: "#3F85A4",
          onClick: (args) => {
            app.updateColor(args.source, args.item.color);
          }
        },
        {
          text: "Green",
          icon: "icon icon-green",
          color: "#6aa84f",
          onClick: (args) => {
            app.updateColor(args.source, args.item.color);
          }
        },
        {
          text: "Yellow",
          icon: "icon icon-yellow",
          color: "#ecb823",
          onClick: (args) => {
            app.updateColor(args.source, args.item.color);
          }
        },
        {
          text: "Red",
          icon: "icon icon-red",
          color: "#ee5530",
          onClick: (args) => {
            app.updateColor(args.source, args.item.color);
          }
        },

      ]
    })
  });
  dp.init();


  const app = {
    async 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");
    },
    init() {
      dp.rows.load("/api/resources");
      dp.events.load("/api/events");
    }
  };

  app.init();


</script>



</body>
</html>

Complete Spring MVC Controller

Here is the source code of the Spring controller that defines the backend API of our scheduling application (MainController.java):


package org.daypilot.demo.html5schedulerspring.controller;

import java.time.LocalDateTime;

import jakarta.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)

The Event class defines the object that stores event data (Event.java).

Note that we use JSON annotations to format the JSON output (the resource JSON property will store the resource id).

package org.daypilot.demo.html5schedulerspring.domain;

import java.time.LocalDateTime;

import jakarta.persistence.*;

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;

	@Column(name = "event_start")
	LocalDateTime start;

	@Column(name = "event_end")
	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; }
}

The Resource class defines the details of the resources that will be displayed as rows (Resource.java).

package org.daypilot.demo.html5schedulerspring.domain;

import jakarta.persistence.Entity;
import jakarta.persistence.GeneratedValue;
import jakarta.persistence.GenerationType;
import jakarta.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)

The EventRepository interface lets us access the Event data (EventRepository.java).

In addition to the standard implementation, we need to add a findBetween() method that lets us select event data for the specified date range.

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);
}

The ResourceRepository interface provides data access methods for the Resource data (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>3.3.0</version>
        <relativePath/>
    </parent>
    <groupId>org.daypilot.demo</groupId>
    <artifactId>html5-scheduler-spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <properties>
        <java.version>22</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>