Skip to content

Instantly share code, notes, and snippets.

@code-boxx
Created June 23, 2023 02:25
Show Gist options
  • Save code-boxx/ee4333471196b25886ee0ad6da4e13a4 to your computer and use it in GitHub Desktop.
Save code-boxx/ee4333471196b25886ee0ad6da4e13a4 to your computer and use it in GitHub Desktop.
Javascript Calendar With Events

JAVASCRIPT CALENDAR WITH EVENTS

https://code-boxx.com/simple-pure-javascript-calendar-events/

NOTES

If you want to set the calendar to start the week on Monday instead, set cal.sMon = true in calendar.js.

LICENSE

Copyright by Code Boxx

Permission is hereby granted, free of charge, to any person obtaining a copy of this software and associated documentation files (the "Software"), to deal in the Software without restriction, including without limitation the rights to use, copy, modify, merge, publish, distribute, sublicense, and/or sell copies of the Software, and to permit persons to whom the Software is furnished to do so, subject to the following conditions:

The above copyright notice and this permission notice shall be included in all copies or substantial portions of the Software.

THE SOFTWARE IS PROVIDED "AS IS", WITHOUT WARRANTY OF ANY KIND, EXPRESS OR IMPLIED, INCLUDING BUT NOT LIMITED TO THE WARRANTIES OF MERCHANTABILITY, FITNESS FOR A PARTICULAR PURPOSE AND NONINFRINGEMENT. IN NO EVENT SHALL THE AUTHORS OR COPYRIGHT HOLDERS BE LIABLE FOR ANY CLAIM, DAMAGES OR OTHER LIABILITY, WHETHER IN AN ACTION OF CONTRACT, TORT OR OTHERWISE, ARISING FROM, OUT OF OR IN CONNECTION WITH THE SOFTWARE OR THE USE OR OTHER DEALINGS IN THE SOFTWARE.

/* (A) ENTIRE PAGE */
* {
font-family: Arial, Helvetica, sans-serif;
box-sizing: border-box;
}
body {
position: relative;
padding: 0; margin: 0;
overflow-x: hidden;
}
:root {
--bg-color-a: #3b39af; /* page header + form label */
--bg-color-b: #404040; /* calendar header */
--bg-color-c: #d73a3a; /* buttons */
}
.hide { display: none !important; }
/* (B) PERIOD SELECTOR */
#calPeriod {
padding: 15px;
background: var(--bg-color-a);
}
#calPeriod input, #calPeriod select {
font-size: 24px;
border: 0; outline: none;
color: #fff; background: 0;
cursor: pointer;
}
#calPeriod option { color: #000; }
#calYear {
width: 100px;
margin-left: 10px;
}
/* (C) CALENDAR */
/* (C1) SHARED */
.calHead, .calRow { display: flex; }
.calCell { width: 14.2857%; }
/* (C2) HEADER - DAY NAMES */
.calHead .calCell {
color: #fff; background: var(--bg-color-b);
font-weight: 700; text-align: center;
padding: 15px 0;
}
/* (C3) BODY - INDIVIDUAL DAYS */
.calBlank { background: #eee; }
.calToday { background: #feffd3; }
.calToday .cellDate { color: #ff9b9b; }
.calBody .calCell {
border: 1px solid #f5f5f5;
min-height: 80px;
}
.cellDate, .evt { padding: 5px; }
.cellDate {
font-size: 14px;
font-weight: 700;
color: #b5b5b5;
}
.evt {
font-size: 14px;
white-space: nowrap; text-overflow: ellipsis;
overflow: hidden;
cursor: pointer;
}
/* (D) EVENT FORM */
#calForm {
position: absolute; top: 0; left: 0; z-index: 999;
width: 100vw; height: 100%; min-height: 100vh;
padding: 0; margin: 0; border: 0;
background: rgba(0,0,0,0.5);
}
#calForm:is([open]) {
display: flex; flex-wrap: wrap;
align-items: center; justify-content: center;
}
#calForm form {
position: relative;
width: 450px; padding: 15px;
border-radius: 10px;
background: #fff;
}
#evtClose {
position: absolute;
top: 0; right: 0;
padding: 10px;
color: #b12020;
font-size: 28px;
font-weight: 700;
cursor: pointer;
}
#calForm h2 { margin: 0 0 15px 0; }
#calForm label, #calForm input[type=text], #calForm textarea { width: 100%; }
#calForm label {
color: #a1a1a1;
font-weight: 700;
font-size: 12px;
margin-bottom: 5px;
}
#calForm input[type=text], #calForm textarea {
width: 100%;
padding: 10px;
margin-bottom: 10px;
border: 1px solid #eee;
background: #f5f5f5;
}
#calForm textarea {
height: 100px;
resize: none;
}
#calForm input[type=button], #calForm input[type=submit] {
border: 0; padding: 10px; margin: 10px 2px 0 0;
color: #fff; background: var(--bg-color-c);
cursor: pointer;
}
<!DOCTYPE html>
<html>
<head>
<title>Simple Javascript Calendar</title>
<link href="calendar.css" rel="stylesheet">
<script defer src="calendar.js"></script>
</head>
<body>
<!-- (A) PERIOD SELECTOR -->
<div id="calPeriod">
<input id="calBack" type="button" value="&lt;">
<select id="calMonth"></select>
<input type="number" id="calYear">
<input id="calNext" type="button" value="&gt;">
</div>
<!-- (B) CALENDAR -->
<div id="calWrap"></div>
<!-- (C) EVENT FORM -->
<dialog id="calForm"><form method="dialog">
<div id="evtClose">X</div>
<h2>CALENDAR EVENT</h2>
<label>Date</label>
<input type="text" id="evtDate" readonly>
<label>Details</label>
<textarea id="evtTxt" required></textarea>
<input id="evtDel" type="button" value="Delete">
<input id="evtSave" type="submit" value="Save">
</form></dialog>
</body>
</html>
var cal = {
// (A) PROPERTIES
// (A1) FLAGS & DATA
sMon : false, // week start on monday
data : null, // events for selected period
sDay : 0, sMth : 0, sYear : 0, // selected day month year
// (A2) MONTHS & DAY NAMES
months : [
"January", "February", "March", "April", "May", "June",
"July", "August", "September", "October", "November", "December"
],
days : ["Sun", "Mon", "Tue", "Wed", "Thur", "Fri", "Sat"],
// (A3) HTML ELEMENTS
hMth : null, hYear : null, // month/year selector
hWrap : null, // calendar wrapper
hFormWrap : null, hForm : null, // event form
hfDate : null, hfTxt : null, hfDel : null, // form fields
// (B) INIT CALENDAR
init : () => {
// (B1) 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.hfDate = document.getElementById("evtDate");
cal.hfTxt = document.getElementById("evtTxt");
cal.hfDel = document.getElementById("evtDel");
// (B2) APPEND MONTHS/YEAR
let now = new Date(), nowMth = now.getMonth();
cal.hYear.value = parseInt(now.getFullYear());
for (let i=0; i<12; i++) {
let opt = document.createElement("option");
opt.value = i;
opt.innerHTML = cal.months[i];
if (i==nowMth) { opt.selected = true; }
cal.hMth.appendChild(opt);
}
// (B3) ATTACH CONTROLS
cal.hMth.onchange = cal.draw;
cal.hYear.onchange = cal.draw;
document.getElementById("calBack").onclick = () => cal.pshift();
document.getElementById("calNext").onclick = () => cal.pshift(1);
cal.hForm.onsubmit = cal.save;
document.getElementById("evtClose").onclick = () => cal.hFormWrap.close();
cal.hfDel.onclick = cal.del;
// (B4) START - DRAW CALENDAR
if (cal.sMon) { cal.days.push(cal.days.shift()); }
cal.draw();
},
// (C) SHIFT CURRENT PERIOD BY 1 MONTH
pshift : forward => {
cal.sMth = parseInt(cal.hMth.value);
cal.sYear = parseInt(cal.hYear.value);
if (forward) { cal.sMth++; } else { cal.sMth--; }
if (cal.sMth > 11) { cal.sMth = 0; cal.sYear++; }
if (cal.sMth < 0) { cal.sMth = 11; cal.sYear--; }
cal.hMth.value = cal.sMth;
cal.hYear.value = cal.sYear;
cal.draw();
},
// (D) DRAW CALENDAR FOR SELECTED MONTH
draw : () => {
// (D1) DAYS IN MONTH + START/END DAYS
// note - jan is 0 & dec is 11
// note - sun is 0 & sat is 6
cal.sMth = parseInt(cal.hMth.value); // selected month
cal.sYear = parseInt(cal.hYear.value); // selected year
let daysInMth = new Date(cal.sYear, cal.sMth+1, 0).getDate(), // number of days in selected month
startDay = new Date(cal.sYear, cal.sMth, 1).getDay(), // first day of the month
endDay = new Date(cal.sYear, cal.sMth, daysInMth).getDay(), // last day of the month
now = new Date(), // current date
nowMth = now.getMonth(), // current month
nowYear = parseInt(now.getFullYear()), // current year
nowDay = cal.sMth==nowMth && cal.sYear==nowYear ? now.getDate() : null ;
// (D2) LOAD DATA FROM LOCALSTORAGE
cal.data = localStorage.getItem("cal-" + cal.sMth + "-" + cal.sYear);
if (cal.data==null) {
localStorage.setItem("cal-" + cal.sMth + "-" + cal.sYear, "{}");
cal.data = {};
} else { cal.data = JSON.parse(cal.data); }
// (D3) DRAWING CALCULATIONS
// (D3-1) BLANK SQUARES BEFORE START OF MONTH
let squares = [];
if (cal.sMon && startDay != 1) {
let blanks = startDay==0 ? 7 : startDay ;
for (let i=1; i<blanks; i++) { squares.push("b"); }
}
if (!cal.sMon && startDay != 0) {
for (let i=0; i<startDay; i++) { squares.push("b"); }
}
// (D3-2) DAYS OF THE MONTH
for (let i=1; i<=daysInMth; i++) { squares.push(i); }
// (D3-3) BLANK SQUARES AFTER END OF MONTH
if (cal.sMon && endDay != 0) {
let blanks = endDay==6 ? 1 : 7-endDay;
for (let i=0; i<blanks; i++) { squares.push("b"); }
}
if (!cal.sMon && endDay != 6) {
let blanks = endDay==0 ? 6 : 6-endDay;
for (let i=0; i<blanks; i++) { squares.push("b"); }
}
// (D4) "RESET" CALENDAR
cal.hWrap.innerHTML = `<div class="calHead"></div>
<div class="calBody">
<div class="calRow"></div>
</div>`;
// (D5) CALENDAR HEADER - DAY NAMES
wrap = cal.hWrap.querySelector(".calHead");
for (let d of cal.days) {
let cell = document.createElement("div");
cell.className = "calCell";
cell.innerHTML = d;
wrap.appendChild(cell);
}
// (D6) CALENDAR BODY - INDIVIDUAL DAYS & EVENTS
wrap = cal.hWrap.querySelector(".calBody");
row = cal.hWrap.querySelector(".calRow");
for (let i=0; i<squares.length; i++) {
// (D6-1) GENERATE CELL
let cell = document.createElement("div");
cell.className = "calCell";
if (nowDay==squares[i]) { cell.classList.add("calToday"); }
if (squares[i]=="b") { cell.classList.add("calBlank"); }
else {
cell.innerHTML = `<div class="cellDate">${squares[i]}</div>`;
if (cal.data[squares[i]]) {
cell.innerHTML += "<div class='evt'>" + cal.data[squares[i]] + "</div>";
}
cell.onclick = () => { cal.show(cell); };
}
row.appendChild(cell);
// (D6-2) NEXT ROW
if (i!=(squares.length-1) && i!=0 && (i+1)%7==0) {
row = document.createElement("div");
row.className = "calRow";
wrap.appendChild(row);
}
}
},
// (E) SHOW EDIT EVENT DOCKET FOR SELECTED DAY
show : cell => {
cal.hForm.reset();
cal.sDay = cell.querySelector(".cellDate").innerHTML;
cal.hfDate.value = `${cal.sDay} ${cal.months[cal.sMth]} ${cal.sYear}`;
if (cal.data[cal.sDay] !== undefined) {
cal.hfTxt.value = cal.data[cal.sDay];
cal.hfDel.classList.remove("hide");
} else { cal.hfDel.classList.add("hide"); }
cal.hFormWrap.show();
},
// (F) SAVE EVENT
save : () => {
cal.data[cal.sDay] = cal.hfTxt.value;
localStorage.setItem(`cal-${cal.sMth}-${cal.sYear}`, JSON.stringify(cal.data));
cal.draw();
cal.hFormWrap.close();
return false;
},
// (G) DELETE EVENT FOR SELECTED DATE
del : () => { if (confirm("Delete event?")) {
delete cal.data[cal.sDay];
localStorage.setItem(`cal-${cal.sMth}-${cal.sYear}`, JSON.stringify(cal.data));
cal.draw();
cal.hFormWrap.close();
}}
};
window.onload = cal.init;
Sign up for free to join this conversation on GitHub. Already have an account? Sign in to comment