Features

  • Spring Boot application that displays a web page with JavaScript Scheduler component.
  • 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)
  • Includes a trial version of DayPilot Pro for JavaScript (see also License below).

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. Buy a license.

Basic Scheduler Configuration (HTML5)

html5-javascript-scheduler-spring-boot-java-initialization.png

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>
    var dp = new DayPilot.Scheduler("dp");
    dp.eventHeight = 40;
    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-rows.png

The Scheduler rows (resources) are defined in the database and we will load them using an AJAX 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.png

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 September 2017 the request URL will look like this:

http://localhost:8081/api/events?start=2017-09-01T00:00:00&end=2017-10-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":"2017-09-03T00:00:00","end":"2017-09-09T00:00:00","color":null,"resource":2}
]

Creating New Events using Drag and Drop

html5-javascript-scheduler-spring-boot-java-creating-event.png

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 AJAX call.

<script>
    var dp = new DayPilot.Scheduler("dp");
    // ...
    dp.onTimeRangeSelected = function (args) {
        DayPilot.Modal.prompt("Create a new event:", "Event").then(function (modal) {
            var dp = args.control;
            dp.clearSelection();
            if (!modal.result) {
                return;
            }
            var params = {
                start: args.start.toString(),
                end: args.end.toString(),
                text: modal.result,
                resource: args.resource
            };
            $.ajax({
                type: 'POST',
                url: '/api/events/create',
                data: JSON.stringify(params),
                success: function (data) {
                    dp.events.add(new DayPilot.Event(data));
                    dp.message("Event created");
                },
                contentType: "application/json",
                dataType: 'json'
            });
        });
    }
    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.findOne(params.resource);   	
    	
    	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.png

Now we want to let users change the color of the event using a context menu.

First, we define the context menu using contextMenu property:

<script>
  var dp = new DayPilot.Scheduler("dp");
  // ...  
  dp.contextMenu = new DayPilot.Menu({
      items: [
          {
              text: "Blue",
              icon: "icon icon-blue",
              color: "#1155cc",
              onClick: function(args) { updateColor(args.source, args.item.color); }
          },
          {
              text: "Green",
              icon: "icon icon-green",
              color: "#6aa84f",
              onClick: function(args) { updateColor(args.source, args.item.color); }
          },
          {
              text: "Yellow",
              icon: "icon icon-yellow",
              color: "#f1c232",
              onClick: function(args) { updateColor(args.source, args.item.color); }
          },
          {
              text: "Red",
              icon: "icon icon-red",
              color: "#cc0000",
              onClick: function(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>
  var dp = new DayPilot.Scheduler("dp");
  // ...  
  dp.onBeforeEventRender = function(args) {
    args.data.areas = [
        { 
          top: 6, 
          right: 2, 
          icon: "icon-triangle-down", 
          visibility: "Hover", 
          action: "ContextMenu", 
          style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px 2px 0px 2px; cursor:pointer;"
        }
    ];
  };
  dp.init();
</script>

Our active area now looks like this:

html5-javascript-scheduler-spring-boot-java-event-active-area.png

The context menu items set the event color using an AJAX call to /api/events/setColor:

function updateColor(e, color) {
    var params = {
        id: e.id(),
        color: color
    };
    $.ajax({
        type: 'POST',
        url: '/api/events/setColor',
        data: JSON.stringify(params),
        success: function (data) {
            e.data.color = color;
            dp.events.update(e);
            dp.message("Color updated");
        },
        contentType: "application/json",
        dataType: 'json'
    });
}

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.findOne(params.id);
        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.png

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":"2017-09-03T00:00:00","end":"2017-09-09T00:00:00","color":"#6aa84f","resource":2}
]

We need to map it to the barColor property of the event data object:

dp.onBeforeEventRender = function(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="icons/main.css" rel="stylesheet" type="text/css">
    <link href="icons/style.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 HTML5/JavaScript Scheduler in Spring Boot (Java)</a></h1>
    <div><a href="https://javascript.daypilot.org/">DayPilot for JavaScript</a> - AJAX Calendar/Scheduling Widgets forJavaScript/HTML5/jQuery/AngularJS</div>
</div>

<div class="main">
    <div id="dp"></div>
</div>

<!-- DayPilot library -->
<script src="js/daypilot/daypilot-all.min.js"></script>
<!-- jQuery -->
<script src="js/jquery/jquery-2.2.0.min.js"></script>

<script>
    var dp = new DayPilot.Scheduler("dp");
    dp.eventHeight = 40;
    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 = function (args) {
        DayPilot.Modal.prompt("Create a new event:", "Event").then(function (modal) {
            var dp = args.control;
            dp.clearSelection();
            if (!modal.result) {
                return;
            }
            var params = {
                start: args.start.toString(),
                end: args.end.toString(),
                text: modal.result,
                resource: args.resource
            };
            $.ajax({
                type: 'POST',
                url: '/api/events/create',
                data: JSON.stringify(params),
                success: function (data) {
                    dp.events.add(new DayPilot.Event(data));
                    dp.message("Event created");
                },
                contentType: "application/json",
                dataType: 'json'
            });
        });
    }
    dp.onEventMove = function (args) {
        var params = {
            id: args.e.id(),
            start: args.newStart.toString(),
            end: args.newEnd.toString(),
            resource: args.newResource
        };
        $.ajax({
            type: 'POST',
            url: '/api/events/move',
            data: JSON.stringify(params),
            success: function (data) {
                dp.message("Event moved");
            },
            contentType: "application/json",
            dataType: 'json'
        });
    };
    dp.onEventResize = function (args) {
        var params = {
            id: args.e.id(),
            start: args.newStart.toString(),
            end: args.newEnd.toString(),
            resource: args.e.resource()
        };
        $.ajax({
            type: 'POST',
            url: '/api/events/move',
            data: JSON.stringify(params),
            success: function (data) {
                dp.message("Event resized");
            },
            contentType: "application/json",
            dataType: 'json'
        });
    };
    dp.onBeforeEventRender = function(args) {
        args.data.barColor = args.data.color;
        args.data.areas = [
            { top: 6, right: 2, icon: "icon-triangle-down", visibility: "Hover", action: "ContextMenu", style: "font-size: 12px; background-color: #f9f9f9; border: 1px solid #ccc; padding: 2px 2px 0px 2px; cursor:pointer;"}
        ];
    };
    dp.contextMenu = new DayPilot.Menu({
        items: [
            {
                text: "Blue",
                icon: "icon icon-blue",
                color: "#1155cc",
                onClick: function(args) { updateColor(args.source, args.item.color); }
            },
            {
                text: "Green",
                icon: "icon icon-green",
                color: "#6aa84f",
                onClick: function(args) { updateColor(args.source, args.item.color); }
            },
            {
                text: "Yellow",
                icon: "icon icon-yellow",
                color: "#f1c232",
                onClick: function(args) { updateColor(args.source, args.item.color); }
            },
            {
                text: "Red",
                icon: "icon icon-red",
                color: "#cc0000",
                onClick: function(args) { updateColor(args.source, args.item.color); }
            },

        ]
    });
    dp.init();

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


    function updateColor(e, color) {
        var params = {
            id: e.id(),
            color: color
        };
        $.ajax({
            type: 'POST',
            url: '/api/events/setColor',
            data: JSON.stringify(params),
            success: function (data) {
                e.data.color = color;
                dp.events.update(e);
                dp.message("Color updated");
            },
            contentType: "application/json",
            dataType: 'json'
        });
    }
</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.findOne(params.resource);   	
    	
    	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.findOne(params.id);   	
    	Resource r = rr.findOne(params.resource);
    	    	
    	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.findOne(params.id);
    	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.AUTO)
	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.AUTO)
	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>1.5.7.RELEASE</version>
    </parent>
    <groupId>org.daypilot.demo</groupId>
    <artifactId>html5-scheduler-spring</artifactId>
    <version>0.0.1-SNAPSHOT</version>

    <!-- add: -->
    <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>