Features

This tutorial shows how to implement your own hotel room booking system using DayPilot Pro JavaScript Scheduler component. It has a modern JavaScript/HTML5 frontend which communicates with the server using a REST API implemented in PHP/MySQL.

  • Use drag and drop to create, move and update reservations

  • See the reservations and available slots using a scheduling calendar component

  • Delete reservations using a built-in icon

  • Edit reservation details using a modal dialog

  • Overlapping reservations are blocked to prevent overbooking

  • Each hotel room has a status (Ready, Cleanup, Dirty) which is highlighted using a color code

  • The receptionists can filter the hotel room by capacity

  • Each reservation has a status (New, Confirmed, Arrived, CheckedOut, Expired), highlighted using a custom bar color

  • The server-side REST API is implemented in PHP (source code included) that uses MySQL to store the data

  • The front desk user interface is built using HTML5 Scheduler that provides a visual overview of all reservations

Other versions of this tutorial.

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.

Scheduler UI Builder

javascript scheduler configurator ui builder

NEW: You can use Scheduler UI Builder online app to create a quick prototype of your new scheduling project. Configure the Scheduler UI in minutes and generate a downloadable HTML5/JavaScript project. See also a tutorial on using the scheduler configurator.

JavaScript Scheduler Initialization

html5 hotel room booking javascript php mysql initialization

We will start the implementation of the booking system front-end by creating a new JavaScript Scheduler instance. The Scheduler is a web calendar component that will display the reservation data for all rooms. In the first step, we will create an empty Scheduler without any data:

HTML5/JavaScript

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

<script>
  const dp = new DayPilot.Scheduler("dp");
  dp.init();
</script>

The default configuration displays an empty timeline with the default scale (one cell per hour) and without any resources (rooms).

Adjusting Scheduler Scale and Time Headers

html5 hotel room booking javascript php mysql scale time headers

Our hotel reservation application will work with days as the basic time unit. We need to switch the Scheduler scale to "Days" and display months and days in the time header rows.

The first time header row will display the month (groupBy: "Month") and the second time header row will display days of month (groupBy: "Day"). We will also specify custom format for the month (format: "MMMM yyyy") because the default formatting pattern only uses the month name.

<div id="dp"></div>
      
<script type="text/javascript">

  const dp = new DayPilot.Scheduler("dp", {
    startDate: DayPilot.Date.today().firstDayOfMonth(),
    days: DayPilot.Date.today().daysInMonth(),
    scale: "Day",
    timeHeaders: [
      { groupBy: "Month", format: "MMMM yyyy" },
      { groupBy: "Day", format: "d" }
    ],
  });
  dp.init();

</script>

Loading Hotel Rooms

html5 hotel room booking javascript php mysql loading rooms

We want to display the hotel rooms as Scheduler resources on the vertical (Y) axis. The room data is stored in a MySQL database and we need to load it from the server using an API call. We can use DayPilot.Http.get() to load the room data using an HTTP call:

JavaScript

async function loadRooms() {
    const {data} = await DayPilot.Http.get("backend_rooms.php");
    dp.update({resources: data});
}

The Scheduler also provides a shortcut rows.load() method that lets us simplify the room loading function:

function loadRooms() {
  dp.rows.load("backend_rooms.php");
}

The server-side backend_rooms.php script returns the hotel room data in JSON format. A sample response looks like this (five hotel rooms are defined):

[
  {"id":"1","name":"Room 1","capacity":2,"status":"Dirty"},
  {"id":"2","name":"Room 2","capacity":2,"status":"Cleanup"},
  {"id":"3","name":"Room 3","capacity":2,"status":"Ready"},
  {"id":"4","name":"Room 4","capacity":4,"status":"Ready"},
  {"id":"5","name":"Room 5","capacity":1,"status":"Ready"}
]

The PHP script loads the rooms from MySQL database and returns a JSON array. In addition to the required properties (id and name), the response also includes additional room data (status and capacity) which we will use later to customize the front desk view.

backend_rooms.php

<?php
require_once '_db.php';

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

$capacity = isset($params->capacity) ? $params->capacity : '0';

$stmt = $db->prepare("SELECT * FROM rooms WHERE capacity = :capacity OR :capacity = '0' ORDER BY name");
$stmt->bindParam(':capacity', $capacity); 
$stmt->execute();
$rooms = $stmt->fetchAll();

class Room {}

$result = array();

foreach($rooms as $room) {
  $r = new Room();
  $r->id = $room['id'];
  $r->name = $room['name'];
  $r->capacity = intval($room['capacity']);
  $r->status = $room['status'];
  $result[] = $r;
}

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

Hotel Room Capacity and Status

PHP Hotel Room Booking System (JavaScript, HTML5, MySQL)   Room Status Icon

Our application stores additional room information in the database (capacity, status). We want to display it in the reservation overview in custom row header columns. The columns can be defined using the rowHeaderColumns property.

JavaScript

{
  rowHeaderColumns = [
    {title: "Room", display:"name"},
    {title: "Capacity", display: "capacity"},
    {title: "Status", display: "status"}
  ],
  // ...
}

The display property of rowHeaderColumns items specifies the resource/row property that will be used for the column content. The resources are defined as follows (we load the rooms using rows.load() method above):

[
  {"id":"1","name":"Room 1","capacity":2,"status":"Dirty"},
  {"id":"2","name":"Room 2","capacity":2,"status":"Cleanup"},
  {"id":"3","name":"Room 3","capacity":2,"status":"Ready"},
  {"id":"4","name":"Room 4","capacity":4,"status":"Ready"},
  {"id":"5","name":"Room 5","capacity":1,"status":"Ready"}
]

We will further customize the row header cells using onBeforeRowHeaderRender event handler. We will use it to customize the "Capacity" column text and add a custom icon to the rows specifying the room status:

JavaScript

onBeforeRowHeaderRender: (args) => {
    const beds = (count) => {
        return `${count} bed${count > 1 ? "s" : ""}`;
    };

    args.row.columns[1].html = beds(args.row.data.capacity);

    args.row.columns[0].areas = [
        {right: 3, top: 3, width: 16, height: 16, cssClass: "area-menu", action: "ContextMenu", visibility: "Hover"}
    ];

    args.row.columns[2].areas = [
        {left: "calc(50% - 10px)", top: 20, width: 20, height: 20, fontColor: "#ffffff", padding: 2, style: "border-radius: 50%;" },
        // status text
        {left: 0, top: 45, right: 0, bottom: 0, html: args.row.data.status, style: "text-align: center;"},
    ];

    const icon = args.row.columns[2].areas[0];

    switch (args.row.data.status) {
        case "Ready":
            icon.symbol = "icons/daypilot.svg#checkmark-2";
            icon.backColor = "#6bbf45";
            break;
        case "Cleanup":
            icon.symbol = "icons/daypilot.svg#figure";
            icon.backColor = "#f5c447";
            break;
        case "Dirty":
            icon.symbol = "icons/daypilot.svg#x-2";
            icon.backColor = "#fa3f3f";
            break;
    }

},

The status icons are added to the third column using active areas that display an SVG symbol icon. You can find the list of bundled SVG icons in the icon preview.

Loading Reservations from MySQL Database

html5 hotel room booking javascript php mysql loading reservations

Now we want to load the reservations (events) from a MySQL database. Again, we will use a call to the REST API and update the view as soon as the response arrives.

JavaScript

async function loadReservations() {
  const start = dp.visibleStart();
  const end = dp.visibleEnd();

  const {data} = await DayPilot.Http.get(`backend_reservations.php?start=${start}&end=${end}`);
  dp.update({events: data});
}

We can also replace the HTTP call with the built-in events.load() method that loads the events from the specified URL (the start and end query string parameters with the visible start and end dates will be added automatically):

dp.events.load("backend_reservations.php");

The server-side backend_reservations.php script returns a JSON array with booking data. Sample JSON output:

[
  {
    "id": "2",
    "text": "Mr. García",
    "start": "2021-09-06T12:00:00",
    "end": "2021-09-11T12:00:00",
    "resource": "2",
    "bubbleHtml": "Reservation details: <br/>Mr. García",
    "status": "Confirmed",
    "paid": "0"
  },
  {
    "id": "3",
    "text": "Mrs. Jones",
    "start": "2021-09-05T12:00:00",
    "end": "2021-09-09T12:00:00",
    "resource": "4",
    "bubbleHtml": "Reservation details: <br/>Mrs. Jones",
    "status": "New",
    "paid": "0"
  },
  {
    "id": "4",
    "text": "Mr. Gray",
    "start": "2021-09-02T12:00:00",
    "end": "2021-09-06T12:00:00",
    "resource": "3",
    "bubbleHtml": "Reservation details: <br/>Mr. Gray",
    "status": "CheckedOut",
    "paid": "100"
  },
  {
    "id": "5",
    "text": "Mrs. Schwartz",
    "start": "2021-09-07T12:00:00",
    "end": "2021-09-12T12:00:00",
    "resource": "3",
    "bubbleHtml": "Reservation details: <br/>Mrs. Schwartz",
    "status": "New",
    "paid": "0"
  },
  {
    "id": "6",
    "text": "Mr. Laurent",
    "start": "2021-09-04T12:00:00",
    "end": "2021-09-09T12:00:00",
    "resource": "1",
    "bubbleHtml": "Reservation details: <br/>Mr. Laurent",
    "status": "Arrived",
    "paid": "50"
  }
]

The PHP backend endpoint uses the start and end query string parameters to filter the reservation records.

backend_reservations.php

<?php
require_once '_db.php';

$start = $_GET['start'];
$end = $_GET['end'];

$stmt = $db->prepare("SELECT * FROM reservations WHERE NOT ((end <= :start) OR (start >= :end))");
$stmt->bindParam(':start', $start);
$stmt->bindParam(':end', $end);
$stmt->execute();
$result = $stmt->fetchAll();

class Event {}
$events = array();

date_default_timezone_set("UTC");
$now = new DateTime("now");
$today = $now->setTime(0, 0, 0);

foreach($result as $row) {
    $e = new Event();
    $e->id = $row['id'];
    $e->text = $row['name'];
    $e->start = $row['start'];
    $e->end = $row['end'];
    $e->resource = $row['room_id'];
    $e->bubbleHtml = "Reservation details: <br/>".$e->text;
    
    // additional properties
    $e->status = $row['status'];
    $e->paid = intval($row['paid']);
    $events[] = $e;
}

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

You can see that we are loading a couple of additional reservation parameters (status, paid) so we can display them in the front desk view.

Loading the rooms and reservations using the built-in helper methods (rows.load() and events.load()) is convenient but it results in two full Scheduler updates. For best performance, you can make the HTTP requests manually and wait for both of them to complete before updating the Scheduler (just once).

Displaying Room Reservation Status

html5 hotel room booking javascript php mysql reservation status

The reservation box appearance can be customized using onBeforeEventRender event handler. We will set custom duration bar color and text, depending on the reservation status.

onBeforeEventRender: (args) => {
    const start = new DayPilot.Date(args.data.start);
    const end = new DayPilot.Date(args.data.end);

    const today = DayPilot.Date.today();
    const now = new DayPilot.Date();

    args.data.html = `${DayPilot.Util.escapeHtml(args.data.text)} (${start.toString("M/d/yyyy")} - ${end.toString("M/d/yyyy")})`;

    switch (args.data.status) {
        case "New":
            const in2days = today.addDays(1);

            if (start < in2days) {
                args.data.barColor = "#cc0000";
                args.data.toolTip = "Expired (not confirmed in time)";
            }
            else {
                args.data.barColor = '#e69138';
                args.data.toolTip = "New";
            }
            break;
        case "Confirmed":
            const arrivalDeadline = today.addHours(18);

            if (start < today || (start.getDatePart() === today.getDatePart() && now > arrivalDeadline)) { // must arrive before 6 pm
                args.data.barColor = "#cc4125";  // red
                args.data.toolTip = "Late arrival";
            }
            else {
                args.data.barColor = "#38761d";
                args.data.toolTip = "Confirmed";
            }
            break;
        case 'Arrived': // arrived
            const checkoutDeadline = today.addHours(10);

            if (end < today || (end.getDatePart() === today.getDatePart() && now > checkoutDeadline)) { // must checkout before 10 am
                args.data.barColor = "#cc4125";  // red
                args.data.toolTip = "Late checkout";
            }
            else
            {
                args.data.barColor = "#1691f4";  // blue
                args.data.toolTip = "Arrived";
            }
            break;
        case 'CheckedOut': // checked out
            args.data.barColor = "gray";
            args.data.toolTip = "Checked out";
            break;
        default:
            args.data.toolTip = "Unexpected state";
            break;
    }

    args.data.html = `<div>${args.data.html}<br /><span style='color:gray'>${args.data.toolTip}</span></div>`;

    const paid = args.data.paid;
    const paidColor = "#aaaaaa";

    args.data.areas = [
        { bottom: 10, right: 4, html: `<div style='color:${paidColor}; font-size: 8pt;'>Paid: ${paid}%</div>`, v: "Visible"},
        { left: 4, bottom: 8, right: 4, height: 2, html: `<div style='background-color:${paidColor}; height: 100%; width:${paid}%'></div>`, v: "Visible" }
    ];

}

Filtering Hotel Rooms by Capacity

html5 hotel room booking javascript php mysql room filter

Our room booking application will support filtering the hotel rooms by capacity. We can implement the room filter using a simple drop-down list.

For a detailed tutorial on more advanced room filtering, please see JavaScript/HTML5 Scheduler: Filtering Rooms by Availability. It explains how to implement multi-parameter filters and find rooms of a specified size that are available for a given date

HTML5/JavaScript

Room filter:
<select id="filter">
    <option value="0">All</option>
    <option value="1">Single</option>
    <option value="2">Double</option>
    <option value="4">Family</option>
</select>

<script>
  const app = {
    elements = {
      filter: document.querySelector("#filter"),
      // ...
    },
    addEventHandlers() {
      app.elements.filter.addEventListener("change", (e) => {
          app.loadRooms();
      });
    }

  };
</script>

html5 hotel room booking javascript php mysql room filter single

The filtering logic is implemented on the server side, in the backend_rooms.php script. We need to extend loadRooms() method and backend_rooms.php to support the room capacity filter.

The selected filter value is added to the request URL as capacity query string parameter.

JavaScript

async function loadRooms() {
    const {data} = await DayPilot.Http.post("backend_rooms.php", { capacity: elements.filter.value });
    dp.update({resources: data});
}

backend_rooms.php

<?php
require_once '_db.php';

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

$capacity = isset($params->capacity) ? $params->capacity : '0';

$stmt = $db->prepare("SELECT * FROM rooms WHERE capacity = :capacity OR :capacity = '0' ORDER BY name");
$stmt->bindParam(':capacity', $capacity); 
$stmt->execute();
$rooms = $stmt->fetchAll();

class Room {}

$result = array();

foreach($rooms as $room) {
  $r = new Room();
  $r->id = $room['id'];
  $r->name = $room['name'];
  $r->capacity = intval($room['capacity']);
  $r->status = $room['status'];
  $result[] = $r;
}

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

Creating Room Reservations using Drag and Drop

html5 hotel room booking javascript php mysql create reservation

The front desk workers should be able to create a new room reservation using drag and drop. Drag and drop time range selecting is already enabled, we just need to add the event handler.

JavaScript

onTimeRangeSelected: async (args) => {

    const rooms = dp.resources.map((item) => {
        return {
            name: item.name,
            id: item.id
        };
    });

    const form = [
        {name: "Text", id: "text"},
        {name: "Start", id: "start", dateFormat: "MM/dd/yyyy HH:mm tt"},
        {name: "End", id: "end", dateFormat: "MM/dd/yyyy HH:mm tt"},
        {name: "Room", id: "resource", options: rooms},
    ];

    const data = {
        start: args.start,
        end: args.end,
        resource: args.resource
    };

    const modal = await DayPilot.Modal.form(form, data);

    dp.clearSelection();
    if (modal.canceled) {
        return;
    }
    const e = modal.result;

    const {data: result} = await DayPilot.Http.post("backend_reservation_create.php", e);

    e.id = result.id;
    e.paid = result.paid;
    e.status = result.status;
    dp.events.add(e);

},

The event handler opens a modal dialog built using DayPilot.Modal.form(). That will let users enter the reservations details:

html5 hotel room booking javascript php mysql new reservation modal

As soon as the user hits [OK] we create the new reservation using an HTTP call to backend_reservation_create.php script.

backend_reservation_create.php

<?php
require_once '_db.php';

$stmt = $db->prepare("INSERT INTO reservations (name, start, end, room_id, status, paid) VALUES (:name, :start, :end, :room, 'New', 0)");
$stmt->bindParam(':start', $_POST['start']);
$stmt->bindParam(':end', $_POST['end']);
$stmt->bindParam(':name', $_POST['name']);
$stmt->bindParam(':room', $_POST['room']);
$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);

Editing Room Reservation Details

html5 hotel room booking javascript php mysql edit reservation modal

In order to let the receptionists edit the reservation details we add a new event click handler that opens a modal dialog with reservation information.

onEventClick: async (args) => {
    const rooms = dp.resources.map((item) => {
        return {
            name: item.name,
            id: item.id
        };
    });

    const statuses = [
        {name: "New", id: "New"},
        {name: "Confirmed", id: "Confirmed"},
        {name: "Arrived", id: "Arrived"},
        {name: "CheckedOut", id: "CheckedOut"}
    ];

    const paidoptions = [
        {name: "0%", id: 0},
        {name: "50%", id: 50},
        {name: "100%", id: 100},
    ]

    const form = [
        {name: "Text", id: "text"},
        {name: "Start", id: "start", dateFormat: "MM/dd/yyyy h:mm tt"},
        {name: "End", id: "end", dateFormat: "MM/dd/yyyy h:mm tt"},
        {name: "Room", id: "resource", options: rooms},
        {name: "Status", id: "status", type: "select", options: statuses},
        {name: "Paid", id: "paid", type: "select", options: paidoptions},
    ];

    const data = args.e.data;

    const modal = await DayPilot.Modal.form(form, data);

    dp.clearSelection();
    if (modal.canceled) {
        return;
    }
    const e = modal.result;
    await DayPilot.Http.post("backend_reservation_update.php", e);
    dp.events.update(e);
},

The modal dialog logic is the same as in case of creating new reservations. We update the reservation details in the database using an HTTP call to backend_reservation_update.php.

backend_reservation_update.php

<?php
require_once '_db.php';

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

$stmt = $db->prepare("UPDATE reservations SET name = :name, start = :start, end = :end, room_id = :room, status = :status, paid = :paid WHERE id = :id");

$stmt->bindParam(':id', $params->id);
$stmt->bindParam(':start', $params->start);
$stmt->bindParam(':end', $params->end);
$stmt->bindParam(':name', $params->text);
$stmt->bindParam(':room', $params->resource);
$stmt->bindParam(':status', $params->status);
$stmt->bindParam(':paid', $params->paid);
$stmt->execute();

class Result {}

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

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

Moving Reservations using Drag and Drop

html5 hotel room booking javascript php mysql moving reservation drag drop

The reservations can be moved using drag and drop. Whenever the reservation is dropped at a new location, onEventMoved handler is fired.

JavaScript

onEventMoved: async (args) => {
    const params = {
        id: args.e.id(),
        newStart: args.newStart,
        newEnd: args.newEnd,
        newResource: args.newResource
    };

    const {data} = await DayPilot.Http.post("backend_reservation_move.php", params);
    dp.message(data.message);
},

We call backend_reservation_move.php REST API endpoint to save the new reservation date in the MySQL database.

backend_reservation_move.php

<?php
require_once '_db.php';

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

class Result {}

$stmt = $db->prepare("SELECT * FROM reservations WHERE NOT ((end <= :start) OR (start >= :end)) AND id <> :id AND room_id = :resource");
$stmt->bindParam(':start', $params->newStart);
$stmt->bindParam(':end', $params->newEnd);
$stmt->bindParam(':id', $params->id);
$stmt->bindParam(':resource', $params->newResource);
$stmt->execute();
$overlaps = $stmt->rowCount() > 0;

if ($overlaps) {
    $response = new Result();
    $response->result = 'Error';
    $response->message = 'This reservation overlaps with an existing reservation.';

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

$stmt = $db->prepare("UPDATE reservations SET start = :start, end = :end, room_id = :resource WHERE id = :id");
$stmt->bindParam(':start', $params->newStart);
$stmt->bindParam(':end', $params->newEnd);
$stmt->bindParam(':id', $params->id);
$stmt->bindParam(':resource', $params->newResource);
$stmt->execute();


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

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

Visualize Hotel Room Occupancy

javascript scheduler displaying group availability

In the JavaScript Scheduler: Displaying Group Availability tutorial, you will see how to visualize room availability and occupancy by date. This tutorial calculates the total number of resources available for a group and displays the number of free rooms.

Instead of using the group row to show the occupancy data, you can also add the histogram to a special frozen row at the top or bottom of the Scheduler.

Highlight Related Reservations

javascript scheduler highlighting related events

In your hotel management application, you may want to link room reservations that are related (group of guests that book multiple rooms).

Preventing Reservation Overlaps

html5 hotel room booking javascript php mysql reservation overlaps

As we don't want to allow overlapping reservations (double booking of a room) we will disable event overlaps.

{
  allowEventOverlap: false,
  // ...
}

This will prevent moving or resizing a reservation over existing reservations and selecting a date that has been already booked.

Deleting Room Reservations

html5 hotel room booking javascript php mysql deleting reservations

The reservations can be deleted using an icon in the upper-right event box. 

We need to enabled this feature using eventDeleteHandling property and add an onEventDeleted handler.

JavaScript

{
  eventDeleteHandling: "Update",
  onEventDeleted: async (args) => {
    await DayPilot.Http.post("backend_reservation_delete.php", {id: args.e.id()});
    dp.message("Deleted.");
  },
  // ...
}

The event handler will call backend_reservation_delete.php to save the changes in the database.

backend_reservation_delete.php

<?php
require_once '_db.php';

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

$stmt = $db->prepare("DELETE FROM reservations WHERE id = :id");
$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);

Changing the Date

html5 hotel room booking javascript php mysql changing date

We will let the front end clerks change the visible date by selecting it using a Navigator control.

HTML5

<div id="nav"></div>

JavaScript

const nav = new DayPilot.Navigator("nav", {
    showMonths: 3,
    skipMonths: 3,
    selectMode: "Month",
    onTimeRangeSelected: (args) => {
        dp.timeline = app.loadTimeline(args.start);
        dp.update();
        app.loadReservations();
    }
});
nav.init();

html5 hotel room booking javascript php mysql date range

The receptionists will also be able to change the visible date range using a drop-down list.

HTML5

Time range: 
<select id="timerange">
    <option value="week">Week</option>
    <option value="month" selected>Month</option>
</select>

JavaScript

app.elements.timerange.addEventListener("change", () => {
    switch (app.elements.timerange.value) {
        case "week":
            app.days = 7;
            nav.selectMode = "Week";
            nav.select(nav.selectionDay);
            break;
        case "month":
            app.days = dp.startDate.daysInMonth();
            nav.selectMode = "Month";
            nav.select(nav.selectionDay);
            break;
    }
});

Hotel Room Check-In and Check-Out Time

html5 hotel room booking javascript php mysql standard check in

Normally, the Scheduler generates the timeline automatically from startDate and days properties. The day cells in the standard timeline always start at 00:00 and end and 24:00.

html5 hotel room booking javascript php mysql custom check in

Hotel reservation systems need to work with custom check-in and check-out times which are usually around noon. In order to display the check-in/check-out deadlines we will create the Scheduler timeline manually and create the timeline cells one-by-one. The check-in and the check-out times are set to 12:00 (noon).

const app = {
    loadTimeline(date) {
        const timeline = [];
        const start = date.getDatePart().addHours(12);

        for (let i = 0; i < app.days; i++) {
            timeline.push({start: start.addDays(i), end: start.addDays(i+1)});
        }
        return timeline;
    },
    // ...
};

It is necessary to use a time header with groupBy: "Day" modifier. The default value (groupBy: "Cell") would align the day header with the grid cells.

{
  timeline: app.loadTimeline(DayPilot.Date.today().firstDayOfMonth()),
  timeHeaders = [
    { groupBy: "Month", format: "MMMM yyyy" },
    { groupBy: "Day", format: "d" }
  ],
  // ...
}

Database (MySQL, SQLite)

The sample project includes a PDO implementation of the database access for two databases:

  • MySQL (_db_mysql.php)

  • SQLite (_db_sqlite.php)

The default database engine (_db.php) is SQLite:

<?php

// use sqlite
require_once '_db_sqlite.php';

// use MySQL
//require_once '_db_mysql.php';

You can switch to MySQL by changing _db.php as follows:

 <?php

// use sqlite
// require_once '_db_sqlite.php';

// use MySQL
require_once '_db_mysql.php';

Don't forget to adjust the database connection properties (server, database name, username, password, port) in _db_mysql.php.

MySQL Database Schema

The database schema is simple - there are only two tables (rooms and reservations). The _db_mysql.php script will create the tables automatically if they don’t exist yet.

The rooms table stores room data:

CREATE TABLE IF NOT EXISTS rooms (
  id INTEGER PRIMARY KEY AUTO_INCREMENT, 
  name TEXT, 
  capacity INTEGER,
  status VARCHAR(30));

The reservations table stores reservation details:

CREATE TABLE IF NOT EXISTS reservations (
  id INTEGER PRIMARY KEY AUTO_INCREMENT, 
  name TEXT, 
  start DATETIME, 
  end DATETIME,
  room_id INTEGER,
  status VARCHAR(30),
  paid INTEGER);

Related Tutorials

The PHP Restaurant Table Reservation Application tutorial shows how to implement a reservations system for a hotel restaurant.

In the PHP Tennis Court Reservation tutorial, you will see how to create a reservation system for hotel sport facilities.

History

  • December 2, 2023: DayPilot Pro 2023.4.5820; room status SVG icons added; toolbar styling improved; dropdown list event handlers fixed

  • July 28, 2022: Parallel HTTP request merging hint; room occupancy chart; highlighting related events

  • June 7, 2021: DayPilot Pro 2021.2.5000, ES6 syntax, using async/await for promise calls, using system fonts

  • November 3, 2020: DayPilot Pro 2020.4.4736, room sorting, room context menu, room deleting, modal dialogs use DayPilot.Modal.form(), XSS protection, jQuery removed

  • May 13, 2020: DayPilot Pro version updated (2020.2.4459), using Tabular mode.

  • October 21, 2019: Minor fixes. Inline code snippets highlighted.

  • September 6, 2019: DayPilot Pro version updated (2019.3.3997). Updated look. New screenshots.

  • May 22, 2019: DayPilot Pro version updated (2019.2.3851). Event HTML updated.

  • February 17, 2018: DayPilot Pro version updated (2018.1.3169). _db.php using requireonce to select DB backend (SQLite/MySQL)

  • January 26, 2017:  DayPilot Pro version updated (8.2.2661).

  • June 8, 2016: onBeforeEventRender fixed (missing "now" declaration). Updated with the latest DayPilot Pro version (8.2.2200).

  • December 8, 2015: MySQL DB schema fixed (auto incrementing room id).

  • November 16, 2015: DayPilot bug fixed (moving existing events over today).

  • November 12, 2015: Navigator added, room creating and editing.

  • October 1, 2015: Incorrect start and end dates in loadEvents() fixed.

  • September 30, 2014: Initial release, based on Hotel Room Booking Tutorial (ASP.NET, C#, VB.NET, SQL Server)