Features

This tutorial shows how to implement a simple hotel room booking system using DayPilot Pro JavaScript Scheduler component in a PHP/HTML5 web application.

  • Drag and drop reservation creating, moving and resizing
  • Reservation deleting
  • Reservation editing using a modal dialog
  • Preventing reservations overlaps
  • Room status (Ready, Cleanup, Dirty) 
  • Room filtering by capacity
  • Reservation status (New, Confirmed, Arrived, CheckedOut, Expired), highlighted using duration bar color
  • MySQL and SQLite databases supported
  • Uses HTML5 scheduler UI control
  • PHP source code
  • Includes a trial version of DayPilot Pro for JavaScript

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.

Scheduler Initialization

html5-hotel-room-booking-initialization.png

We will create a new instance of the JavaScript Scheduler using the following code.

HTML5/JavaScript

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

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

It will display an empty Scheduler with the default scale and no resources (rooms) defined.

Adjusting Scheduler Scale and Time Headers

html5-hotel-room-booking-scale-time-headers.png

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

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

  var dp = new DayPilot.Scheduler("dp");
  dp.scale = "Day";
  dp.timeHeaders = [
      { groupBy: "Month", format: "MMMM yyyy" },
      { groupBy: "Day", format: "d" }
  ];
  dp.days = 30;
  dp.init();

</script>

Highlighting Weekends

html5-hotel-room-booking-highlight-weekends.png

Now we will highlight weekends by handling onBeforeCellRender event.

JavaScript

dp.onBeforeCellRender = function(args) {
  var dayOfWeek = args.cell.start.getDayOfWeek();
  if (dayOfWeek === 6 || dayOfWeek === 0) {
      args.cell.backColor = "#f8f8f8";
  }
};

Loading Rooms to the Scheduler

html5-hotel-room-booking-load-rooms.png

In this step, we will load the rooms (resources) from the server using an AJAX call. We will use jQuery $.post() method:

JavaScript

function loadResources() {
  $.post("backend_rooms.php", 
  function(data) {
      dp.resources = data;
      dp.update();
  });
}

The server-side backend_rooms.php script is a simple JSON endpoint that returns the room data in the following format:

[
  {"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"}
]

backend_rooms.php

<?php
require_once '_db.php';

$stmt = $db->prepare("SELECT * FROM rooms ORDER BY name");
$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 = $room['capacity'];
  $r->status = $room['status'];
  $result[] = $r;
  
}

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

?>

Room Capacity and Status (Columns)

html5-hotel-room-booking-columns.png

We want to display additional information about the rooms (capacity, status) so we need to define the additional columns.

JavaScript

dp.rowHeaderColumns = [
  {title: "Room", width: 80},
  {title: "Capacity", width: 80},
  {title: "Status", width: 80}
];

We will use onBeforeResHeaderRender event to map the room data to the additional columns.

JavaScript

dp.onBeforeResHeaderRender = function(args) {
  var beds = function(count) {
      return count + " bed" + (count > 1 ? "s" : "");
  };
  
  args.resource.columns[0].html = beds(args.resource.capacity);
  args.resource.columns[1].html = args.resource.status;
  switch (args.resource.status) {
      case "Dirty":
          args.resource.cssClass = "status_dirty";
          break;
      case "Cleanup":
          args.resource.cssClass = "status_cleanup";
          break;
  }
};

We are using the "scheduler_white" CSS theme:

dp.theme = "scheduler_white";

We need to define the custom CSS classes we used in onBeforeResHeaderRender to highlight the the room status ("status_dirty", "status_cleanup"):

<style type="text/css">
  .scheduler_white_rowheader 
  {
      background: -webkit-gradient(linear, left top, left bottom, from(#eeeeee), to(#dddddd));
          background: -moz-linear-gradient(top, #eeeeee 0%, #dddddd);
          background: -ms-linear-gradient(top, #eeeeee 0%, #dddddd);
          background: -webkit-linear-gradient(top, #eeeeee 0%, #dddddd);
          background: linear-gradient(top, #eeeeee 0%, #dddddd);
          filter: progid:DXImageTransform.Microsoft.gradient(startColorStr="#eeeeee", endColorStr="#dddddd");

  }
  .scheduler_white_rowheader_inner 
  {
          border-right: 1px solid #ccc;
  }
  .scheduler_white_rowheadercol2
  {
      background: White;
  }
  .scheduler_white_rowheadercol2 .scheduler_white_rowheader_inner 
  {
      top: 2px;
      bottom: 2px;
      left: 2px;
      background-color: transparent;
          border-left: 5px solid #1a9d13; /* green */
          border-right: 0px none;
  }
  .status_dirty.scheduler_white_rowheadercol2 .scheduler_white_rowheader_inner
  {
          border-left: 5px solid #ea3624; /* red */
  }
  .status_cleanup.scheduler_white_rowheadercol2 .scheduler_white_rowheader_inner
  {
          border-left: 5px solid #f9ba25; /* orange */
  }

</style>

Loading Reservations from a Database

html5-hotel-room-booking-loading-reservations.png

Now we want to load the reservations (events) from the database. Again, we will use a simple jQuery AJAX call.

JavaScript

function loadEvents() {
  var start = dp.visibleStart();
  var end = dp.visibleEnd();

  $.post("backend_events.php", 
      {
          start: start.toString(),
          end: end.toString()
      },
      function(data) {
          dp.events.list = data;
          dp.update();
      }
  );
}

The server-side backend_event.php script returns a JSON array with reservation details. Sample JSON output:

[
  {"id":"2","text":"Mr. Gray","start":"2014-09-29 00:00:00","end":"2014-10-05 00:00:00","resource":"3","bubbleHtml":"Reservation details: <br\/>Mr. Gray","status":"New","paid":"0"},
  {"id":"4","text":"Mrs. Garc\u00eda","start":"2014-09-29 00:00:00","end":"2014-10-05 00:00:00","resource":"1","bubbleHtml":"Reservation details: <br\/>Mrs. Garc\u00eda","status":"Arrived","paid":"0"},
  {"id":"7","text":"Mr. Jones","start":"2014-09-29 00:00:00","end":"2014-10-03 00:00:00","resource":"2","bubbleHtml":"Reservation details: <br\/>Mr. Jones","status":"CheckedOut","paid":"100"}
]

The PHP backend endpoint filters the reservations using the start and end query string parameters

backend_events.php

<?php
require_once '_db.php';

$stmt = $db->prepare("SELECT * FROM reservations WHERE NOT ((end <= :start) OR (start >= :end))");
$stmt->bindParam(':start', $_POST['start']);
$stmt->bindParam(':end', $_POST['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 = $row['paid'];
    $events[] = $e;
}

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

?>

Filtering Rooms by Capacity

html5-hotel-room-booking-filter.png

We will add a room filter based on a simple drop-down list.

HTML5/JavaScript

Show rooms:
<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>
  $(document).ready(function() {
    $("#filter").change(function() {
        loadResources();
    });
  });
</script>

html5-hotel-room-booking-filtered-rooms.png

We need to extend loadResources() method and backend_rooms.php to support the room capacity filter.

JavaScript

function loadResources() {
  $.post("backend_rooms.php", 
  { capacity: $("#filter").val() },
  function(data) {
      dp.resources = data;
      dp.update();
  });
}

backend_rooms.php

<?php
require_once '_db.php';

$stmt = $db->prepare("SELECT * FROM rooms WHERE capacity = :capacity OR :capacity = '0' ORDER BY name");
$stmt->bindParam(':capacity', $_POST['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 = $room['capacity'];
  $r->status = $room['status'];
  $result[] = $r;
  
}

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

?>

Creating Reservations using Drag and Drop

html5-hotel-room-booking-create-drag-drop.png

The end users 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

dp.onTimeRangeSelected = function (args) {
  //var name = prompt("New event name:", "Event");
  //if (!name) return;

  var modal = new DayPilot.Modal();
  modal.closed = function() {
      dp.clearSelection();
      
      // reload all events
      var data = this.result;
      if (data && data.result === "OK") {
          loadEvents();
      }
  };
  modal.showUrl("new.php?start=" + args.start + "&end=" + args.end + "&resource=" + args.resource);
  
};

The event handler opens a new page (new.php) in a modal dialog using DayPilot.Modal class.

html5-hotel-room-booking-new-reservation-dialog.png

new.php

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>New Reservation</title>
    	<link type="text/css" rel="stylesheet" href="media/layout.css" />    
        <script src="js/jquery/jquery-1.9.1.min.js" type="text/javascript"></script>
    </head>
    <body>
        <?php
            // check the input
            //is_numeric($_GET['id']) or die("invalid URL");
            
            require_once '_db.php';
            
            $rooms = $db->query('SELECT * FROM rooms');
            
            $start = $_GET['start']; // TODO parse and format
            $end = $_GET['end']; // TODO parse and format
        ?>
        <form id="f" action="backend_create.php" style="padding:20px;">
            <h1>New Reservation</h1>
            <div>Name: </div>
            <div><input type="text" id="name" name="name" value="" /></div>
            <div>Start:</div>
            <div><input type="text" id="start" name="start" value="<?php echo $start ?>" /></div>
            <div>End:</div>
            <div><input type="text" id="end" name="end" value="<?php echo $end ?>" /></div>
            <div>Room:</div>
            <div>
                <select id="room" name="room">
                    <?php 
                        foreach ($rooms as $room) {
                            $selected = $_GET['resource'] == $room['id'] ? ' selected="selected"' : '';
                            $id = $room['id'];
                            $name = $room['name'];
                            print "<option value='$id' $selected>$name</option>";
                        }
                    ?>
                </select>
                
            </div>
            <div class="space"><input type="submit" value="Save" /> <a href="javascript:close();">Cancel</a></div>
        </form>
        
        <script type="text/javascript">
        function close(result) {
            if (parent && parent.DayPilot && parent.DayPilot.ModalStatic) {
                parent.DayPilot.ModalStatic.close(result);
            }
        }

        $("#f").submit(function () {
            var f = $("#f");
            $.post(f.attr("action"), f.serialize(), function (result) {
                close(eval(result));
            });
            return false;
        });

        $(document).ready(function () {
            $("#name").focus();
        });
    
        </script>
    </body>
</html>

This page contains a simple HTML form. We need to close the modal dialog on the submission so we intercept it using jQuery. We will submit it using a special AJAX call ($.post) and close the modal dialog using parent.DayPilot.ModalStatic.close().

The backend_create.php is the AJAX endpoint for creating a new reservation in the database.

backend_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 Reservation Details

html5-hotel-room-booking-edit-reservation.png

In order to let the users edit the reservation details we create a new event click handler that will open a modal dialog with reservation details.

dp.onEventClick = function(args) {
  var modal = new DayPilot.Modal();
  modal.closed = function() {
      // reload all events
      var data = this.result;
      if (data && data.result === "OK") {
          loadEvents();
      }
  };
  modal.showUrl("edit.php?id=" + args.e.id());
};

edit.php

<!DOCTYPE html>
<html>
    <head>
        <meta charset="UTF-8">
        <title>Edit Event</title>
    	<link type="text/css" rel="stylesheet" href="media/layout.css" />    
        <script src="js/jquery/jquery-1.9.1.min.js" type="text/javascript"></script>
    </head>
    <body>
        <?php
            // check the input
            is_numeric($_GET['id']) or die("invalid URL");
            
            require_once '_db.php';
            
            $stmt = $db->prepare('SELECT * FROM reservations WHERE id = :id');
            $stmt->bindParam(':id', $_GET['id']);
            $stmt->execute();
            $reservation = $stmt->fetch();
            
            $rooms = $db->query('SELECT * FROM rooms');
        ?>
        <form id="f" action="backend_update.php" style="padding:20px;">
            <input type="hidden" name="id" value="<?php print $_GET['id'] ?>" />
            <h1>Edit Reservation</h1>
            <div>Start:</div>
            <div><input type="text" id="start" name="start" value="<?php print $reservation['start'] ?>" /></div>
            <div>End:</div>
            <div><input type="text" id="end" name="end" value="<?php print $reservation['end'] ?>" /></div>
            <div>Room:</div>
            <div>
                <select id="room" name="room">
                    <?php 
                        foreach ($rooms as $room) {
                            $selected = $reservation['room_id'] == $room['id'] ? ' selected="selected"' : '';
                            $id = $room['id'];
                            $name = $room['name'];
                            print "<option value='$id' $selected>$name</option>";
                        }
                    ?>
                </select>
                
            </div>
            <div>Name: </div>
            <div><input type="text" id="name" name="name" value="<?php print $reservation['name'] ?>" /></div>
            <div>Status:</div>
            <div>
                <select id="status" name="status">
                    <?php 
                        $options = array("New", "Confirmed", "Arrived", "CheckedOut");
                        foreach ($options as $option) {
                            $selected = $option == $reservation['status'] ? ' selected="selected"' : '';
                            $id = $option;
                            $name = $option;
                            print "<option value='$id' $selected>$name</option>";
                        }
                    ?>
                </select>                
            </div>
            <div>Paid:</div>
            <div>
                <select id="paid" name="paid">
                    <?php 
                        $options = array(0, 50, 100);
                        foreach ($options as $option) {
                            $selected = $option == $reservation['paid'] ? ' selected="selected"' : '';
                            $id = $option;
                            $name = $option."%";
                            print "<option value='$id' $selected>$name</option>";
                        }
                    ?>
                </select>
                
            </div>
            
            <div class="space"><input type="submit" value="Save" /> <a href="javascript:close();">Cancel</a></div>
        </form>
        
        <script type="text/javascript">
        function close(result) {
            if (parent && parent.DayPilot && parent.DayPilot.ModalStatic) {
                parent.DayPilot.ModalStatic.close(result);
            }
        }

        $("#f").submit(function () {
            var f = $("#f");
            $.post(f.attr("action"), f.serialize(), function (result) {
                close(eval(result));
            });
            return false;
        });

        $(document).ready(function () {
            $("#name").focus();
        });
    
        </script>
    </body>
</html>

The modal dialog logic is the same as in the case of creating new reservations. We intercept the form submission and save the data using a special AJAX call to backend_update.php.

backend_update.php

<?php
require_once '_db.php';

$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', $_POST['id']);
$stmt->bindParam(':name', $_POST['name']);
$stmt->bindParam(':start', $_POST['start']);
$stmt->bindParam(':end', $_POST['end']);
$stmt->bindParam(':room', $_POST['room']);
$stmt->bindParam(':status', $_POST['status']);
$stmt->bindParam(':paid', $_POST['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-move-drag-drop.png

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

JavaScript

dp.onEventMoved = function (args) {
  $.post("backend_move.php", 
  {
      id: args.e.id(),
      newStart: args.newStart.toString(),
      newEnd: args.newEnd.toString(),
      newResource: args.newResource
  }, 
  function(data) {
      dp.message(data.message);
  });
};

We will call backend_move.php using AJAX to save the new reservation date.

backend_move.php

<?php
require_once '_db.php';

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', $_POST['newStart']);
$stmt->bindParam(':end', $_POST['newEnd']);
$stmt->bindParam(':id', $_POST['id']);
$stmt->bindParam(':resource', $_POST['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(':id', $_POST['id']);
$stmt->bindParam(':start', $_POST['newStart']);
$stmt->bindParam(':end', $_POST['newEnd']);
$stmt->bindParam(':resource', $_POST['newResource']);
$stmt->execute();


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

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

?>

Preventing Reservation Overlaps

html5-hotel-room-booking-overlapping-reservation.png

As we don't want to allow overlapping reservations (double booking of a room) we turn off event overlapping.

dp.allowEventOverlap = false;

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

Deleting Reservations

html5-hotel-room-booking-delete-reservation.png

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

dp.eventDeleteHandling = "Update";

dp.onEventDeleted = function(args) {
  $.post("backend_delete.php", 
  {
      id: args.e.id()
  }, 
  function() {
      dp.message("Deleted.");
  });
};

The event handler will call backend_delete.php using AJAX.

backend_delete.php

<?php
require_once '_db.php';

$stmt = $db->prepare("DELETE FROM reservations WHERE id = :id");
$stmt->bindParam(':id', $_POST['id']);
$stmt->execute();

class Result {}

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

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

?>

Displaying Reservation Status

html5-hotel-room-booking-reservation-status.png

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

dp.onBeforeEventRender = function(args) {
  var start = new DayPilot.Date(args.e.start);
  var end = new DayPilot.Date(args.e.end);
  
  var today = new DayPilot.Date().getDatePart();
  var now = new DayPilot.Date();
  
  args.e.html = args.e.text + " (" + start.toString("M/d/yyyy") + " - " + end.toString("M/d/yyyy") + ")"; 
  
  switch (args.e.status) {
      case "New":
          var in2days = today.addDays(1);
          
          if (start.getTime() < in2days.getTime()) {
              args.e.barColor = 'red';
              args.e.toolTip = 'Expired (not confirmed in time)';
          }
          else {
              args.e.barColor = 'orange';
              args.e.toolTip = 'New';
          }
          break;
      case "Confirmed":
          var arrivalDeadline = today.addHours(18);

          if (start.getTime() < today.getTime() || (start.getTime() === today.getTime() && now.getTime() > arrivalDeadline.getTime())) { // must arrive before 6 pm
              args.e.barColor = "#f41616";  // red
              args.e.toolTip = 'Late arrival';
          }
          else {
              args.e.barColor = "green";
              args.e.toolTip = "Confirmed";
          }
          break;
      case 'Arrived': // arrived
          var checkoutDeadline = today.addHours(10);

          if (end.getTime() < today.getTime() || (end.getTime() === today.getTime() && now.getTime() > checkoutDeadline.getTime())) { // must checkout before 10 am
              args.e.barColor = "#f41616";  // red
              args.e.toolTip = "Late checkout";
          }
          else
          {
              args.e.barColor = "#1691f4";  // blue
              args.e.toolTip = "Arrived";
          }
          break;
      case 'CheckedOut': // checked out
          args.e.barColor = "gray";
          args.e.toolTip = "Checked out";
          break;
      default:
          args.e.toolTip = "Unexpected state";
          break;    
  }
  
  args.e.html = args.e.html + "<br /><span style='color:gray'>" + args.e.toolTip + "</span>";
  
  var paid = args.e.paid;
  var paidColor = "#aaaaaa";

  args.e.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" }
  ];

};

Changing the Date

html5-hotel-room-booking-changing-date-navigator.png

We will let the users change the visible date by selecting it using a Navigator control.

HTML5

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

JavaScript

<script type="text/javascript">
  var nav = new DayPilot.Navigator("nav");
  nav.selectMode = "Month";
  nav.showMonths = 3;
  nav.skipMonths = 3;
  nav.onTimeRangeSelected = function(args) {
      loadTimeline(args.start);
      loadEvents();
  };
  nav.init();
</script>

html5-hotel-room-booking-javascript-time-range.png

We will also let them 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

$("#timerange").change(function() {
  switch (this.value) {
      case "week":
          dp.days = 7;
          nav.selectMode = "Week";
          nav.select(nav.selectionDay);
          break;
      case "month":
          dp.days = dp.startDate.daysInMonth();
          nav.selectMode = "Month";
          nav.select(nav.selectionDay);
          break;
  }
  loadTimeline(DayPilot.Date.today());
  loadEvents();
});

Check-In and Check-Out Time

html5-hotel-room-booking-javascript-php-standard-check-in.png

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-custom-check-in.png

Hotel reservation systems work with overnight cells with custom check-in and check-out time. We will create the Scheduler timeline manually and set custom cell start and end. The check-in and the check-out times are set to 12:00 (noon).

function loadTimeline(date) {
  dp.scale = "Manual";
  dp.timeline = [];
  var start = date.getDatePart().addHours(12);
  
  for (var i = 0; i < dp.days; i++) {
      dp.timeline.push({start: start.addDays(i), end: start.addDays(i+1)});
  }
  dp.update();
}

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:

dp.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 engines:

  • MySQL (_db_mysql.php)
  • SQLite (_db_sqlite.php)

The default engine (_db.php) is SQLite. 

You can switch to MySQL by replacing the _db.php file with _db_mysql.php. Don't forget to adjust the database connection properties (server, database name, username, password, port).

The MySQL script will create 'rooms' and 'reservations' tables in the target database (if they don't exist already).

MySQL Database Schema

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

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