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)

html5 javascript scheduler spring boot java initialization

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

html5 javascript scheduler spring boot java rows

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)

html5 javascript scheduler spring boot java 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

html5 javascript scheduler spring boot java event creation

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

html5 javascript scheduler spring boot java 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:

html5 javascript scheduler spring boot java event active area

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


}

html5 javascript scheduler spring boot java event bar

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>