Welcome to a tutorial on how to build an AJAX-driven events calendar with PHP and MySQL. Looking to add some organizational features to your project? Too many calendars out there that are too complicated? A simple PHP MySQL calendar only consists of a few key components: - A database table to store the events.
- A PHP class library to
manage the events.
- Lastly, an HTML page to show the calendar of events.
But just how is this done exactly? Let us walk through a step-by-step example in this guide – Read on! ⓘ I have included a zip file with all the source code at the start of this tutorial, so you don’t have to copy-paste everything… Or if you just want to dive straight in. TABLE OF CONTENTSDOWNLOAD & NOTESFirstly, here is the download link to the example code as promised. QUICK NOTES- Create a database and import
1-database.sql . - Change the database settings in
2-cal-lib.php to your own. - That’s all – Launch
4a-cal-page.php in your browser.
If you spot a bug, feel free to comment below. I try to answer short questions too, but
it is one person versus the entire world… If you need answers urgently, please check out my list of websites to get help with programming. SCREENSHOT
EXAMPLE CODE DOWNLOADClick here to download the source code, I have released it under the MIT license, so feel free to build on top of it or use it in your own project. All right, let us now get into the details
of building a PHP MYSQL calendar. PART 1) EVENTS DATABASE TABLE1-database.sql CREATE TABLE `events` (
`evt_id` bigint(20) NOT NULL,
`evt_start` datetime NOT NULL,
`evt_end` datetime NOT NULL,
`evt_text` text NOT NULL,
`evt_color` varchar(7) NOT NULL,
`evt_bg` varchar(7) NOT NULL
) ENGINE=InnoDB DEFAULT CHARSET=utf8mb4;
ALTER TABLE `events`
ADD PRIMARY KEY (`evt_id`),
ADD KEY `evt_start` (`evt_start`),
ADD KEY `evt_end` (`evt_end`);
ALTER TABLE `events`
MODIFY `evt_id` bigint(20) NOT NULL AUTO_INCREMENT, AUTO_INCREMENT=1;
Let us start by dealing with the foundation of the system – A database table to save all the event entries.
Field
| Description
| evt_id
| Primary key, auto-increment.
| evt_start
| Event start date.
| evt_end
| Event end date.
| evt_text
| Details of the event.
| evt_color
| Color of the text.
| evt_color
| Background color.
|
PART 2) PHP CALENDAR LIBRARY2A) INITIALIZE2-cal-lib.php <?php
class Calendar {
// (A) CONSTRUCTOR - CONNECT TO DATABASE
private $pdo = null;
private $stmt = null;
public $error = "";
function __construct () { try {
$this->pdo = new PDO(
"mysql:host=".DB_HOST.";dbname=".DB_NAME.";charset=".DB_CHARSET,
DB_USER, DB_PASSWORD, [
PDO::ATTR_ERRMODE => PDO::ERRMODE_EXCEPTION,
PDO::ATTR_DEFAULT_FETCH_MODE => PDO::FETCH_ASSOC
]);
} catch (Exception $ex) { exit($ex->getMessage()); }}
// (B) DESTRUCTOR - CLOSE DATABASE CONNECTION
function __destruct () {
if ($this->stmt!==null) { $this->stmt = null; }
if ($this->pdo!==null) { $this->pdo = null; }
}
// (C) HELPER - RUN SQL QUERY
function query ($sql, $data=null) {
$this->stmt = $this->pdo->prepare($sql);
$this->stmt->execute($data);
}
// ...
}
// (G) DATABASE SETTINGS - CHANGE TO YOUR OWN!
define("DB_HOST", "localhost");
define("DB_NAME", "test");
define("DB_CHARSET", "utf8");
define("DB_USER", "root");
define("DB_PASSWORD", "");
// (H) SUNDAY FIRST
define("SUN_FIRST", true);
// (I) NEW CALENDAR OBJECT
$_CAL = new Calendar();
This library looks massive at first, but keep calm and look carefully. The top and bottom sections are actually straightforward. - (A, B, I) When
$_CAL = new Calendar() is created, the constructor connects to the database. The destructor closes the connection. - (C)
query() is a helper function to run an SQL query.
- (G) Remember to change the database settings to your own.
- (H) Change
SUN_FIRST to false if you want the week to start on Monday.
2B) EVENT FUNCTIONS2-cal-lib.php class Calendar {
// (D) SAVE EVENT
function save ($start, $end, $txt, $color, $bg, $id=null) {
// (D1) START & END DATE CHECK
if (strtotime($end) < strtotime($start)) { $this->error = "End date cannot be earlier than start date";
return false;
}
// (D2) RUN SQL
if ($id==null) {
$sql = "INSERT INTO `events` (`evt_start`, `evt_end`, `evt_text`, `evt_color`, `evt_bg`) VALUES (?,?,?,?,?)";
$data = [$start, $end, $txt, $color, $bg];
} else {
$sql = "UPDATE `events` SET `evt_start`=?, `evt_end`=?, `evt_text`=?, `evt_color`=?, `evt_bg`=? WHERE `evt_id`=?";
$data = [$start, $end, $txt, $color, $bg, $id];
}
$this->query($sql, $data);
return true;
}
// (E) DELETE EVENT
function del ($id) {
$this->query("DELETE FROM `events` WHERE `evt_id`=?", [$id]);
return true;
}
// (F) GET DATES & EVENTS FOR SELECTED PERIOD
function get ($month, $year) {
// (F1) DATA STRUCTURE
$cells = []; // "b" blank cell
// "d" day number
// "t" today
// "e" => [event ids]
$events = []; // event id => data
$map = []; // "yyyy-mm-dd" => $cells[n]
// (F2) DATE RANGE CALCULATIONS
$month = $month<10 ? "0$month" : $month ; $daysInMonth = cal_days_in_month(CAL_GREGORIAN, $month, $year); $dateYM = "{$year}-{$month}-"; $dateFirst = $dateYM . "01"; $dateLast = $dateYM . $daysInMonth; $dayFirst = (new DateTime($dateFirst))->format("w");
$dayLast = (new DateTime($dateLast))->format("w");
$nowDay = (date("n")==$month && date("Y")==$year) ? date("j") : 0 ;
// (F3) PAD EMPTY CELLS BEFORE FIRST DAY OF MONTH
if (SUN_FIRST) { $pad = $dayFirst; }
else { $pad = $dayFirst==0 ? 6 : $dayFirst-1 ; }
for ($i=0; $i<$pad; $i++) { $cells[] = ["b" => 1]; }
// (F4) DAYS IN MONTH
for ($day=1; $day<=$daysInMonth; $day++) {
$i = count($cells);
$map["$year-$month-".($day<10?"0$day":$day)] = $i; $cells[] = ["d" => $day];
if ($day == $nowDay) { $cells[$i]["t"] = 1; }
}
// (F5) PAD EMPTY CELLS AFTER LAST DAY OF MONTH
if (SUN_FIRST) { $pad = $dayLast==0 ? 6 : 6-$dayLast ; }
else { $pad = $dayLast==0 ? 0 : 7-$dayLast ; }
for ($i=0; $i<$pad; $i++) { $cells[] = ["b" => 1]; }
// (F6) GET EVENTS
$start = "$dateFirst 00:00:00";
$end = "$dateLast 23:59:59";
$this->query("SELECT * FROM `events` WHERE (
(`evt_start` BETWEEN ? AND ?)
OR (`evt_end` BETWEEN ? AND ?)
OR (`evt_start` <= ? AND `evt_end` >= ?)
)", [$start, $end, $start, $end, $start, $end]);
while ($r = $this->stmt->fetch()) {
// (F6-1) "SAVE" $EVENTS DETAILS
$events[$r["evt_id"]] = [
"s" => $r["evt_start"], "e" => $r["evt_end"],
"c" => $r["evt_color"], "b" => $r["evt_bg"],
"t" => $r["evt_text"]
];
// (F6-2) "MAP" EVENTS TO $CELLS
$start = substr($r["evt_start"], 5, 2)==$month ? (int)substr($r["evt_start"], 8, 2) : 1 ;
$end = substr($r["evt_end"], 5, 2)==$month ? (int)substr($r["evt_end"], 8, 2) : 1 ;
for ($d=$start; $d<=$end; $d++) {
$eday = $dateYM . ($d<10?"0$d":$d);
if (!isset($cells[$map[$eday]]["e"])) { $cells[$map[$eday]]["e"] = []; }
$cells[$map[$eday]]["e"][] = $r["evt_id"];
}
}
// (F7) RESULTS
return ["s"=>SUN_FIRST, "e" => $events, "c" => $cells];
}
}
There are only 3 “main event functions” here: - (D)
save() Saves an event (insert or update). - (E)
del() Deletes an event. - (F)
get() Gets the date range
and events for a selected month/year. Not going to explain line-by-line, so a quick spotlight on the returned array. "s" Sunday or Monday first. "e" Events in the format of [EVENT ID => EVENT DETAILS] . See (F6-1). "c" The calendar cells in a “raw array form”. With flags to indicate the blank cells, day number, today, and if there are events.
PART 3) CALENDAR AJAX HANDLER3-cal-ajax.php <?php
if (isset($_POST["req"])) {
// (A) LOAD LIBRARY
require "2-cal-lib.php";
switch ($_POST["req"]) {
// (B) INVALID REQUEST
default : echo "Invalid request"; break;
// (C) GET DATES & EVENTS FOR SELECTED PERIOD
case "get":
echo json_encode($_CAL->get($_POST["month"], $_POST["year"]));
break;
// (D) SAVE EVENT
case "save":
echo $_CAL->save(
$_POST["start"], $_POST["end"], $_POST["txt"], $_POST["color"], $_POST["bg"],
isset($_POST["id"]) ? $_POST["id"] : null
) ? "OK" : $_CAL->error ;
break;
// (E) DELETE EVENT
case "del":
echo $_CAL->del($_POST["id"]) ? "OK" : $_CAL->error ;
break;
}
}
This AJAX handler pretty much
“maps $_POST requests to library functions”. For example, we send $_POST["req"] = "del" and $_POST["id"] = 123 to delete an event. PART 4) CALENDAR PAGE4A) HTML PAGE4a-cal-page.php <?php
// (A) DAYS MONTHS YEAR
$months = [
1 => "January", 2 => "Febuary", 3 => "March", 4 => "April",
5 => "May", 6 => "June", 7 => "July", 8 => "August",
9 => "September", 10 => "October", 11 => "November", 12 => "December"
];
$monthNow = date("m");
$yearNow = date("Y"); ?>
<!-- (B) PERIOD SELECTOR -->
<div id="calPeriod">
<select id="calMonth"><?php foreach ($months as $m=>$mth) {
printf("<option value='%u'%s>%s</option>",
$m, $m==$monthNow?" selected":"", $mth
);
} ?></select>
<input id="calYear" type="number" value="<?=$yearNow?>">
<input id="calAdd" type="button" value="+">
</div>
<!-- (C) CALENDAR WRAPPER -->
<div id="calWrap"></div>
<!-- (D) EVENT FORM -->
<div id="calForm"><form>
<div id="evtCX">X</div>
<input type="hidden" id="evtID">
<label>Date Start</label>
<input id="evtStart" type="datetime-local" required>
<label>Date End</label>
<input id="evtEnd" type="datetime-local" required>
<label>Event</label>
<textarea id="evtTxt" required></textarea>
<label>Text Color</label>
<input id="evtColor" type="color" value="#000000" required>
<label>Background Color</label>
<input id="evtBG" type="color" value="#ffdbdb" required>
<input type="submit" id="evtSave" value="Save">
<input type="button" id="evtDel" value="Delete">
</form></div>
- Month and year
“stuff”.
<div id="calPeriod"> The month and year selector. <div id="calWrap"> The calendar itself – This will be generated with Javascript. <form id="calForm"> A hidden popup form to add/update an event.
P.S. I am using native <input type="datetime-local"> and <input type="color"> . Feel free to implement and use your own library if you want – React, Angular, Vue, jQuery, etc… 4B) JAVASCRIPT INITIALIZE4b-calendar.js var cal = {
// (A) PROPERTIES
events : null, // events data for current month/year
hMth : null, hYear : null, // html month & year
hWrap : null, // html calendar wrapper
// html form & fields
hFormWrap : null, hForm : null, hfID : null,
hfStart : null, hfEnd : null, hfTxt : null,
hfColor : null, hfBG : null, hfDel : null,
// (B) SUPPORT FUNCTION - AJAX FETCH
ajax : (data, onload) => {
// (B1) FORM DATA
let form = new FormData();
for (let [k,v] of Object.entries(data)) { form.append(k,v); }
// (B2) FETCH
fetch("3-cal-ajax.php", { method:"POST", body:form })
.then(res=>res.text()).then(onload);
},
// (C) INIT CALENDAR
init : () => {
// (C1) GET HTML ELEMENTS
cal.hMth = document.getElementById("calMonth");
cal.hYear = document.getElementById("calYear");
cal.hWrap = document.getElementById("calWrap");
cal.hFormWrap = document.getElementById("calForm");
cal.hForm = cal.hFormWrap.querySelector("form");
cal.hfID = document.getElementById("evtID");
cal.hfStart = document.getElementById("evtStart");
cal.hfEnd = document.getElementById("evtEnd");
cal.hfTxt = document.getElementById("evtTxt");
cal.hfColor = document.getElementById("evtColor");
cal.hfBG = document.getElementById("evtBG");
cal.hfDel = document.getElementById("evtDel");
// (C2) ATTACH CONTROLS
cal.hMth.onchange = cal.draw;
cal.hYear.onchange = cal.draw;
document.getElementById("calAdd").onclick = () => { cal.show(); };
cal.hForm.onsubmit = () => { return cal.save(); };
document.getElementById("evtCX").onclick = () => { cal.hFormWrap.classList.remove("show"); };
cal.hfDel.onclick = cal.del;
// (C3) DRAW CALENDAR
cal.draw();
},
// ...
};
window.onload = cal.init;
Once again, we have some “long stinky Javascript”. But look
carefully, the first few sections should be self-explanatory. var cal is an object that contains all the “calendar mechanics”. - (A & C) On window load,
cal.init() will run. All it does is to get the HTML elements and “enable” them. - (B)
cal.ajax() is a helper function to do a fetch() call to 3-cal-ajax.php .
4C) JAVASCRIPT DRAW CALENDAR4b-calendar.js // (D) DRAW CALENDAR
draw : () => {
// (D1) FETCH DATA
cal.ajax({
req : "get", month : cal.hMth.value, year : cal.hYear.value
}, data => {
// (D2) "UNPACK DATA"
data = JSON.parse(data);
let cells = data.c, sunFirst = data.s,
wrap, row, cell, evt, i = 0;
cal.events = data.e;
data = ["Mon", "Tue", "Wed", "Thu", "Fri", "Sat"];
if (sunFirst) { data.unshift("Sun"); }
else { data.push("Sun"); }
// (D3) "RESET" CALENDAR
cal.hWrap.innerHTML = `<div class="calHead"></div>
<div class="calBody">
<div class="calRow"></div>
</div>`;
// (D4) CALENDAR HEADER - DAY NAMES
wrap = cal.hWrap.querySelector(".calHead");
for (let d of data) {
cell = document.createElement("div");
cell.className = "calCell";
cell.innerHTML = d;
wrap.appendChild(cell);
}
// (D5) CALENDAR BODY - INDIVIDUAL DAYS & EVENTS
wrap = cal.hWrap.querySelector(".calBody");
row = cal.hWrap.querySelector(".calRow");
for (let c of cells) {
// (D5-1) GENERATE CELL
cell = document.createElement("div");
cell.className = "calCell";
if (c.t) { cell.classList.add("calToday"); }
if (c.b) { cell.classList.add("calBlank"); }
else {
cell.innerHTML = `<div class="cellDate">${c.d}</div>
<div class="cellEvt"></div>`;
}
row.appendChild(cell);
if (c.e) {
cell = cell.querySelector(".cellEvt");
for (let id of c.e) {
evt = document.createElement("div");
evt.className = "evt";
evt.style.color = cal.events[id]["c"];
evt.style.background = cal.events[id]["b"];
evt.innerHTML = cal.events[id]["t"];
evt.onclick = () => { cal.show(id); };
cell.appendChild(evt);
}
}
// (D5-2) NEW ROW
i++;
if (i%7==0 && i!=cells.length) {
row = document.createElement("div");
row.className = "calRow";
wrap.appendChild(row);
}
}
});
},
The next piece of the puzzle – Where did
the calendar come from? Right here. All cal.draw() does is get the calendar data from 3-cal-ajax.php and generate the HTML. 4D) JAVASCRIPT CALENDAR EVENTS4b-calendar.js // (E) SHOW EVENT FORM
show : id => {
if (id) {
cal.hfID.value = id;
cal.hfStart.value = cal.events[id]["s"].replace(" ", "T").substring(0, 16);
cal.hfEnd.value = cal.events[id]["e"].replace(" ", "T").substring(0, 16);
cal.hfTxt.value = cal.events[id]["t"];
cal.hfColor.value = cal.events[id]["c"];
cal.hfBG.value = cal.events[id]["b"];
cal.hfDel.style.display = "block";
} else {
cal.hForm.reset();
cal.hfID.value = "";
cal.hfDel.style.display = "none";
}
cal.hFormWrap.classList.add("show");
},
// (F) SAVE EVENT
save : () => {
// (F1) COLLECT DATA
var data = {
req : "save",
start : cal.hfStart.value,
end : cal.hfEnd.value,
txt : cal.hfTxt.value,
color : cal.hfColor.value,
bg : cal.hfBG.value
};
if (cal.hfID.value != "") { data.id = cal.hfID.value; }
// (F2) DATE CHECK
if (new Date(data.start) > new Date(data.end)) {
alert("Start date cannot be later than end date!");
return false;
}
// (F3) AJAX SAVE
cal.ajax(data, res => { if (res == "OK") {
cal.hFormWrap.classList.remove("show");
cal.draw();
} else { alert(res); }});
return false;
},
// (G) DELETE EVENT
del : () => { if (confirm("Delete event?")) {
cal.ajax({
req : "del", id : cal.hfID.value
}, res => { if (res == "OK") {
cal.hFormWrap.classList.remove("show");
cal.draw();
} else { alert(res); }});
}}
The last few sections of the Javascript deal with calendar events. cal.show() Show the add/edit event form. cal.save() Save the event. cal.del() Delete the event.
PART 5) PROGRESSIVE WEB APP5A) HTML META HEADERS4a-cal-page.php <!-- ANDROID + CHROME + APPLE + WINDOWS APP -->
<meta name="mobile-web-app-capable" content="yes">
<meta name="theme-color" content="white">
<link rel="apple-touch-icon" href="icon-512.png">
<meta name="apple-mobile-web-app-capable" content="yes">
<meta name="apple-mobile-web-app-status-bar-style" content="black">
<meta name="apple-mobile-web-app-title" content="Calendar">
<meta name="msapplication-TileImage" content="icon-512.png">
<meta name="msapplication-TileColor" content="#ffffff">
<!-- WEB APP MANIFEST -->
<!-- https://web.dev/add-manifest/ -->
<link rel="manifest" href="5-manifest.json">
<!-- SERVICE WORKER -->
<script>
if ("serviceWorker" in navigator) {
navigator.serviceWorker.register("5-worker.js");
}
</script>
The system is already complete and this is completely optional. But to turn this into a progressive web app, all it requires are 3 things: - Add some “web app” meta tags in
the HTML head section. I am too lazy to resize the icons and set the colors properly… Change these in your own free time if you want.
- Add a web app manifest.
- Register a service worker.
5B) WEB MANIFEST5-manifest.json {
"short_name": "Calendar",
"name": "Calendar",
"icons": [{
"src": "favicon.png",
"sizes": "64x64",
"type": "image/png"
}, {
"src": "icon-512.png",
"sizes": "512x512",
"type": "image/png"
}],
"start_url": "/4a-calendar.php",
"scope": "/",
"background_color": "white",
"theme_color": "white",
"display": "standalone"
}
The manifest file is what it is, a file that contains information about your web app – App name, icons, theme, settings, etc… 5C) SERVICE WORKER5-worker.js // (A) FILES TO CACHE
const cName = "phpcal",
cFiles = [
"4b-calendar.js",
"4c-calendar.css",
"favicon.png",
"icon-512.png"
];
// (B) CREATE/INSTALL CACHE
self.addEventListener("install", (evt) => {
evt.waitUntil(
caches.open(cName)
.then((cache) => { return cache.addAll(cFiles); })
.catch((err) => { console.error(err) })
);
});
// (C) LOAD FROM CACHE FIRST, FALLBACK TO NETWORK IF NOT FOUND
self.addEventListener("fetch", (evt) => {
evt.respondWith(
caches.match(evt.request)
.then((res) => { return res || fetch(evt.request); })
);
});
For those who are
new – A service worker is simply Javascript that runs in the background. For this service worker: - (A & B) We create a browser cache and save the CSS/JS.
- (C) Hijack the fetch requests. If the requested file is found in the cache, use it. If not, fall back to load from the network.
In other words, this service worker speeds up loading and lessens the server load. COMPATIBILITY CHECKS- Arrow Function – CanIUse
- Template Literals – CanIUse
- Viewport Units – CanIUse
- Add To Home Screen – CanIUse
- Service Worker – CanIUse
This example will work on most modern “Grade A” browsers. LIMITATIONSYes, this is a fully functioning simple events calendar system. But there are plenty of improvements that you can make, plus some limitations to take note of: - The system is open to all users – You will want to integrate some form of user login.
- There is no security,
some encryption may be good if sensitive information is involved.
- Only one calendar instance per page.
LINKS & REFERENCES- Simple PHP Login Without Database – Code Boxx
- Calendar Progress Web App – Code Boxx
- For the people who
want to share events, do mobile integration, set reminders, import/export, and all the “professional features” – That is not a “simple calendar”. Check out Google Calendar and the Calendar API instead.
THE ENDThank you for reading, and we have come to the end of this guide. I hope that it has helped you to build a better project, and if you have anything to share with this guide, please feel free to comment below. Good luck and happy coding!
How to create event calendar using php?
Welcome to a tutorial on how to build an AJAX-driven events calendar with PHP and MySQL.. Create a database and import 1-database. sql .. Change the database settings in 2-cal-lib. php to your own.. That's all – Launch 4a-cal-page. php in your browser..
How to use calendar in php?
PHP Calendar Introduction
The calendar extension contains functions that simplifies converting between different calendar formats. Note: To convert between calendar formats, you must first convert to Julian Day Count, then to the calendar of your choice.
How to create a dynamic event calendar in php?
Create Database Table. To display the dynamic events in the calendar, the event data need to be stored in the database. ... . Database Configuration (dbConfig.php) ... . Helper Functions to Build Event Calendar (functions. ... . Display Event Calendar (index. ... . Testing. ... . Conclusion..
How do I create a calendar event in HTML?
Read more in the resource calendar tutorials for JavaScript, Angular, React and Vue.. Step 1: Event Calendar JavaScript Library. Include daypilot-all. ... . Step 2: Event Calendar Placeholder. ... . Step 3: Initialize the Scheduler. ... . Step 4: Load Data. ... . Step 5: Event Moving. ... . Step 6: Event Editing. ... . Step 7: Apply the CSS Theme..
|