Features

This tutorial uses HTML5/JavaScript event calendar widget from DayPilot Lite for JavaScript package (open-source). 

  • Weekly event calendar

  • Pure JavaScript (no third-party libraries required)

  • Open source (Apache License 2.0)

  • Drag and drop event moving

  • Drag and drop event resizing

  • CSS themes

  • Easy initialization

  • Includes DayPilot Lite for JavaScript 2021.1.261

There is also a DayPilot Pro version available (commercial) that includes advanced features, including HTML5 Scheduler (a timeline for multiple resources).

Sample Project

Frontend:

  • HTML5

  • JavaScript

  • CSS3

Backend:

  • PHP + SQLite database

  • ASP.NET Core (.NET 5, Visual Studio 2019) + SQL Server database (LocalDB) + Entity Framework

License

The source code of this sample project is licensed under the Apache License 2.0.

JavaScript Calendar Quick Initialization

html5-javascript-event-calendar-open-source-initialization.png

The JavaScript calendar requires a placeholder <div>:

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

Remember to include DayPilot Lite JavaScript library (daypilot-all.min.js):

<script src="js/daypilot/daypilot-all.min.js" type="text/javascript"></script>

Use DayPilot.Calendar class to initialize and render the event calendar:

<script>
  const dp = new DayPilot.Calendar("dp");
  dp.viewType = "Week";
  dp.init();
</script>

HTML5 Calendar: Loading Events

html5-javascript-event-calendar-open-source-loading-events.png

Initial Event Set

The event can be loaded during the initialization:

<script>
  const dp = new DayPilot.Calendar("dp");
  dp.viewType = "Week";
  dp.events.list = [
    {
      "start": "2022-01-11T10:30:00",
      "end": "2022-01-11T13:30:00",
      "id": "225eb40f-5f78-b53b-0447-a885c8e92233",
      "text": "Calendar Event 1"
    },
    {
      "start": "2022-01-12T12:30:00",
      "end": "2022-01-12T15:00:00",
      "id": "1f67def5-e1dd-57fc-2d39-eb7a5f8e789a",
      "text": "Calendar Event 2"
    },
    {
      "start": "2022-01-13T10:30:00",
      "end": "2022-01-13T16:00:00",
      "id": "aba78fd9-09d0-642e-612d-0e7e002c29f5",
      "text": "Calendar Event 3"
    }
  ];
  dp.init();
</script>

Load Calendar Events Asynchronously

You can also load the calendar events later. Fill the events.list array and call update().

The following example uses an HTTP call to load the event list from the server and update the event calendar:

function loadEvents() {
  DayPilot.Http.ajax({
    url: "backend_events.php?start=" + dp.visibleStart() + "&end=" + dp.visibleEnd(),   // in .NET, use "/api/CalendarEvents"
    success: function(data) {
        dp.events.list = data;
        dp.update();
    }
  });
}

The HTML5 calendar includes a helper method that will let you load the event data using a single call:

function loadEvents() {
  dp.events.load("backend_events.php");   // in .NET, use "/api/CalendarEvents"
}

The events.load() method automatically adds the start and end dates of the current view to the URL as query string parameters.

Sample PHP backend (backend_events.php):

<?php
require_once '_db.php';
    
$stmt = $db->prepare('SELECT * FROM events WHERE NOT ((end <= :start) OR (start >= :end))');

$stmt->bindParam(':start', $_GET['start']);
$stmt->bindParam(':end', $_GET['end']);

$stmt->execute();
$result = $stmt->fetchAll();

class Event {}
$events = array();

foreach($result as $row) {
  $e = new Event();
  $e->id = $row['id'];
  $e->text = $row['name'];
  $e->start = $row['start'];
  $e->end = $row['end'];
  $events[] = $e;
}

echo json_encode($events);

.NET 5 Backend (CalendarEventsController.cs)

// GET: api/CalendarEvents
[HttpGet]
public async Task<ActionResult<IEnumerable<CalendarEvent>>> GetEvents([FromQuery] DateTime start, [FromQuery] DateTime end)
{
    return await _context.Events
        .Where(e => !((e.End <= start) || (e.Start >= end)))
        .ToListAsync();
}

Adding Individual Events

You can add individual events using events.add() method. This method accepts a raw data object (like in the following example) or a DayPilot.Event object.

dp.events.add({
  start: args.start,
  end: args.end,
  id: DayPilot.guid(),
  resource: args.resource,
  text: name
});

This method is not suitable for adding large number of events because it updates the calendar during every events.add() call.

Date Picker Integration

html5-javascript-event-calendar-open-source-date-picker.png

You can add a date navigator component (on the left) to let users change the visible week.

First we create two columns using CSS.

  • Add the navigator placeholder to the left column (<div id="nav"></div>)

  • Add the event calendar placeholder to the right column (<div id="dp"></div>)

<div style="display: flex;">
  <div style="margin-right: 10px;">
    <div id="nav"></div>
  </div>
  <div style="flex-grow: 1;">
    <div id="dp"></div>
  </div>
</div>

Initialize the navigator using DayPilot.Navigator class:

const nav = new DayPilot.Navigator("nav");
nav.showMonths = 3;
nav.skipMonths = 3;
nav.selectMode = "week";
nav.init();

Handle the onTimeRangeSelected event of the navigator and change the event calendar startDate property to change the visible week:

nav.onTimeRangeSelected = args => {
  dp.startDate = args.day;
  dp.update();
  loadEvents();
};

Event Calendar CSS Themes

The sample project includes 5 sample CSS themes (Default, Green, Google-Like, Traditional, Transparent).

The Default CSS theme is built in. The other themes are defined in standalone css files (e.g. calendar_green.css). You need to include the stylesheet:

<link type="text/css" rel="stylesheet" href="themes/calendar_green.css" />    

You can also create your own CSS theme using the online DayPilot theme designer [themes.daypilot.org].

The following code allows switching the CSS theme on the fly:

<div class="space">CSS Theme: 
  <select id="theme">
    <option value="calendar_default">Default</option>
    <option value="calendar_g">Google-Like</option>
    <option value="calendar_green">Green</option>
    <option value="calendar_traditional">Traditional</option>
    <option value="calendar_transparent">Transparent</option>
    <option value="calendar_white">White</option>
  </select>
</div>

<script type="text/javascript">
  const elements = {
    theme: document.querySelector("#theme")
  };

  elements.theme.addEventListener("change", () => {
    dp.theme = elements.theme.value;
    dp.update();
  }); 
</script>

Default CSS Theme (calendar_default, built-in)

html5-javascript-event-calendar-open-source-theme-default.png

Google-Like CSS Theme (calendar_g.css)

html5-javascript-event-calendar-open-source-theme-g.png

Green CSS Theme (calendar_green.css)

html5-javascript-event-calendar-open-source-theme-green.png

Traditional CSS Theme (calendar_traditional.css)

html5-javascript-event-calendar-open-source-theme-traditional.png

Transparent CSS Theme (calendar_transparent.css)

html5-javascript-event-calendar-open-source-theme-transparent.png

White CSS Theme (calendar_white.css)

html5-javascript-event-calendar-open-source-theme-white.png

Drag and Drop Event Moving

html5-javascript-event-calendar-open-source-drag-drop-moving.png

Drag and drop moving of calendar events is enabled by default. The event calendar updates the event position after drop and fires onEventMoved event. You can use this event to update the database using an AJAX call.

JavaScript (for PHP backend)

dp.onEventMoved = args => {
  DayPilot.Http.ajax({
    url: "backend_move.php",
    data: {
      id: args.e.id(),
      newStart: args.newStart,
      newEnd: args.newEnd
    },
    success: function () {
      console.log("Moved.");
    }
  });
};

JavaScript (for .NET backend)

dp.onEventMoved = args => {
    DayPilot.Http.ajax({
        url: "/api/CalendarEvents/" + args.e.id(),
        method: "PUT",
        data: {
            id: args.e.id(),
            start: args.newStart,
            end: args.newEnd,
            text: args.e.text()
        },
        success: function () {
            console.log("Resized.");
        }
    });
};

PHP backend (backend_move.php)

<?php
require_once '_db.php';

$json = file_get_contents('php://input');
$params = json_decode($json);

$insert = "UPDATE events SET start = :start, end = :end WHERE id = :id";

$stmt = $db->prepare($insert);

$stmt->bindParam(':start', $params->newStart);
$stmt->bindParam(':end', $params->newEnd);
$stmt->bindParam(':id', $params->id);

$stmt->execute();

class Result {}

$response = new Result();
$response->result = 'OK';
$response->message = 'Update successful';

header('Content-Type: application/json');
echo json_encode($response);

ASP.NET Core Backend (CalendarEventsController.cs)

// PUT: api/CalendarEvents/5
[HttpPut("{id}")]
public async Task<IActionResult> PutCalendarEvent(int id, CalendarEvent calendarEvent)
{
    if (id != calendarEvent.Id)
    {
        return BadRequest();
    }

    _context.Entry(calendarEvent).State = EntityState.Modified;

    try
    {
        await _context.SaveChangesAsync();
    }
    catch (DbUpdateConcurrencyException)
    {
        if (!CalendarEventExists(id))
        {
            return NotFound();
        }
        else
        {
            throw;
        }
    }

    return NoContent();
}

Drag and Drop Event Resizing

Drag and drop moving of calendar events is enabled by default. The calendar updates the event duration after drop and fires onEventResized event. You can use this event to update the database using an AJAX call.

JavaScript (for PHP backend)

dp.onEventResized = args => {
  DayPilot.Http.ajax({
    url: "backend_resize.php",
    data: {
      id: args.e.id(),
      newStart: args.newStart,
      newEnd: args.newEnd
    },
    success: function () {
      console.log("Resized.");
    }
  });
};

JavaScript (for .NET backend)

dp.onEventResized = args => {
    DayPilot.Http.ajax({
        url: "/api/CalendarEvents/" + args.e.id(),
        method: "PUT",
        data: {
            id: args.e.id(),
            start: args.newStart,
            end: args.newEnd,
            text: args.e.text()
        },
        success: function () {
            console.log("Resized.");
        }
    });
};

Drag and Drop Event Creating

html5-javascript-event-calendar-open-source-drag-drop-creating.png

New events can be added using events.add() method.

The event calendar allows drag and drop time range selection. It is enabled by default. The onTimeRangeSelected event is fired as soon as the selecting is finished. You can use this event to add the new event to the calendar and update the database using an AJAX call.

The event handler opens a modal dialog using DayPilot Modal. The DayPilot.Modal.form() method lets you open a modal dialog with custom field (you can design your own modal dialog using Modal Dialog Builder).

JavaScript (for PHP backend)

dp.onTimeRangeSelected = async args => {
  const form = [
    {name: "Name", id: "text"}
  ];

  const modal = await DayPilot.Modal.form(form, {});
  dp.clearSelection();

  if (modal.canceled) {
    return;
  }

  DayPilot.Http.ajax({
    url: "backend_create.php",
    data: {
      start: args.start,
      end: args.end,
      text: modal.result.text
    },
    success: function (ajax) {
      const data = ajax.data;
      dp.events.add(new DayPilot.Event({
        start: args.start,
        end: args.end,
        id: data.id,
        text: modal.result.text
      }));
      console.log("Created.");
    }
  });

};

JavaScript (for .NET backend)

dp.onTimeRangeSelected = async args => {
  const form = [
    {name: "Name", id: "text"}
  ];

  const modal = await DayPilot.Modal.form(form, {});
  dp.clearSelection();

  if (modal.canceled) {
    return;
  }

  DayPilot.Http.ajax({
    url: "/api/CalendarEvents",
    method: "POST",
    data: {
      start: args.start,
      end: args.end,
      text: modal.result.text
    },
    success: function (ajax) {
      const data = ajax.data;
      dp.events.add(new DayPilot.Event({
        start: args.start,
        end: args.end,
        id: data.id,
        text: modal.result.text
      }));
      console.log("Created.");
    }
  });

};

PHP Backend (backend_create.php)

<?php
require_once '_db.php';

$json = file_get_contents('php://input');
$params = json_decode($json);

$insert = "INSERT INTO events (name, start, end) VALUES (:name, :start, :end)";

$stmt = $db->prepare($insert);

$stmt->bindParam(':start', $params->start);
$stmt->bindParam(':end', $params->end);
$stmt->bindParam(':name', $params->text);
$stmt->execute();

class Result {}

$response = new Result();
$response->result = 'OK';
$response->message = 'Created with id: '.$db->lastInsertId();
$response->id = $db->lastInsertId();

header('Content-Type: application/json');
echo json_encode($response);

ASP.NET Core Backend (CalendarEventsController.cs)

// POST: api/CalendarEvents
[HttpPost]
public async Task<ActionResult<CalendarEvent>> PostCalendarEvent(CalendarEvent calendarEvent)
{
    _context.Events.Add(calendarEvent);
    await _context.SaveChangesAsync();

    return CreatedAtAction("GetCalendarEvent", new { id = calendarEvent.Id }, calendarEvent);
}

Event Deleting

html5-javascript-event-calendar-open-source-deleting.png

Event deleting is disabled by default but it can be enabled easily using eventDeleteHandling property:

dp.eventDeleteHandling = "Update";

This option will enable the delete icon in the upper-right corner (it becomes visible when you hover over an event). When a user clicks the icon, the event is removed form the calendar.

It also fires onEventDelete and onEventDeleted events which you can use to notify the server (and save the changes in the database).

This feature is available in DayPilot Lite for JavaScript since version 2018.1.228.

JavaScript (for PHP backend):

dp.onEventDeleted = args => {
  DayPilot.Http.ajax({
    url: "backend_delete.php",
    data: {
      id: args.e.id()
    },
    success: function () {
      console.log("Deleted.");
    }
  });
};

JavaScript (for .NET backend):

dp.onEventDeleted = args => {
    DayPilot.Http.ajax({
        url: "/api/CalendarEvents/" + args.e.id(),
        method: "DELETE",
        success: function() {
            console.log("Deleted.");
        }
    });
};

PHP Backend (backend_delete.php)

<?php
require_once '_db.php';

$json = file_get_contents('php://input');
$params = json_decode($json);

$insert = "DELETE FROM events WHERE id = :id";

$stmt = $db->prepare($insert);

$stmt->bindParam(':id', $params->id);

$stmt->execute();

class Result {}

$response = new Result();
$response->result = 'OK';
$response->message = 'Delete successful';

header('Content-Type: application/json');
echo json_encode($response);

ASP.NET Core Backend (CalendarEventsController.cs)

// DELETE: api/CalendarEvents/5
[HttpDelete("{id}")]
public async Task<IActionResult> DeleteCalendarEvent(int id)
{
    var calendarEvent = await _context.Events.FindAsync(id);
    if (calendarEvent == null)
    {
        return NotFound();
    }

    _context.Events.Remove(calendarEvent);
    await _context.SaveChangesAsync();

    return NoContent();
}

.NET 5 Project

The .NET 5 project uses the following NuGet packages:

PM > Install-Package Microsoft.EntityFrameworkCore
PM > Install-Package Microsoft.EntityFrameworkCore.SqlServer
PM > Install-Package Microsoft.EntityFrameworkCore.Tools

The SQL database schema is defined using Entity Framework model class (CalendarEvent):

public class CalendarEvent
{
    public int Id { get; set; }
    public DateTime Start { get; set; }
    public DateTime End { get; set; }
    public string Text { get; set; }
    public string Color { get; set; }
}

Database context (CalendarDbContext class):

public class CalendarDbContext : DbContext
{
    public DbSet<CalendarEvent> Events { get; set; }

    public CalendarDbContext(DbContextOptions<CalendarDbContext> options) : base(options) {}
}

Database connection string (appsettings.json):

{
    // ...
    "ConnectionStrings": {
        "CalendarContext": "Server=(localdb)\\mssqllocaldb;Database=DayPilot.TutorialCalendarOpenSource;Trusted_Connection=True"
    }
}

DB context registration (Startup class):

public class Startup
{
    // This method gets called by the runtime. Use this method to add services to the container.
    public void ConfigureServices(IServiceCollection services)
    {
        services.AddControllersWithViews();
        
        // add: db context registration
        services.AddDbContext<CalendarDbContext>(options => options.UseSqlServer(Configuration.GetConnectionString("CalendarContext")));
    }
    
    // ...

}

The InitialCreate database migration was created using the following command:

Add-Migration InitialCreate

Before running the project, it’s necessary to create the SQL Server database:

Update-Database

PHP Project

This PHP backend uses a SQLite database with a single table (events). The database file will be created and initialized automatically in the application root if it doesn’t exist (daypilot.sqlite file).

Database Schema

CREATE TABLE events (
  id INTEGER PRIMARY KEY, 
  name TEXT, 
  start DATETIME, 
  end DATETIME,
  resource VARCHAR(30)
);

You can find the database initialization script in _db.php file.