Last active
January 25, 2022 11:09
-
-
Save edp1096/1c9b5670dc4decc00096762fbb4661d8 to your computer and use it in GitHub Desktop.
Calendar using knockout.js
This file contains bidirectional Unicode text that may be interpreted or compiled differently than what appears below. To review, open the file in an editor that reveals hidden Unicode characters.
Learn more about bidirectional Unicode characters
<html> | |
<head> | |
<script src="https://cdnjs.cloudflare.com/ajax/libs/moment.js/2.18.0/moment.min.js"></script> | |
<script type="text/javascript" src="https://unpkg.com/[email protected]/build/output/knockout-latest.js"></script> | |
</head> | |
<body> | |
<div class="cal-cavity"> | |
<button onclick="moveThisMonth()">Goto this month</button> | |
<button onclick="resetSelection()">Reset date selection</button> | |
<div class="year-month"> | |
<a href="#" class="month_prev" onclick="movePrevMonth(); return false"><</a> | |
<b data-bind="text: ym">1970 · 13</b> | |
<a href="#" class="month_next" onclick="moveNextMonth(); return false">></a> | |
</div> | |
<table cellpadding="0" cellspacing="0" class="cal-table"> | |
<thead> | |
<tr data-bind="foreach: weekNames"> | |
<!-- ko if: $index() == 0 --> | |
<th class="sun" data-bind="text: $data">Sun</th> | |
<!-- /ko --> | |
<!-- ko if: $index() == 6 --> | |
<th class="sat" data-bind="text: $data">Sat</th> | |
<!-- /ko --> | |
<!-- ko ifnot: $index() == 0 || $index() == 6 --> | |
<th data-bind="text: $data">Mon..Fri</th> | |
<!-- /ko --> | |
</tr> | |
</thead> | |
<tbody data-bind="foreach: daysByWeek"> | |
<tr data-bind="foreach: $data"> | |
<!-- ko ifnot: cal.dateAvailable($data) --> | |
<td class="none"><a data-bind="text: $data.day"></a></td> | |
<!-- /ko --> | |
<!-- ko if: cal.dateAvailable($data) --> | |
<!-- ko if: $index() == 0 --> | |
<td class="sun" data-bind="css: {today: cal.isToday($data.ym,$data.day), selected: cal.dateInSelected($data.ym,$data.day)}"> | |
<a data-bind="text: $data.day, attr: {onclick: 'selectDate('+$data.ym+','+$data.day+')'}"></a> | |
</td> | |
<!-- /ko --> | |
<!-- ko if: $index() == 6 --> | |
<td class="sat" data-bind="css: {today: cal.isToday($data.ym,$data.day), selected: cal.dateInSelected($data.ym,$data.day)}"> | |
<a data-bind="text: $data.day, attr: {onclick: 'selectDate('+$data.ym+','+$data.day+')'}"></a> | |
</td> | |
<!-- /ko --> | |
<!-- ko if: $index() > 0 && $index() < 6 --> | |
<td class="" data-bind="css: {today: cal.isToday($data.ym,$data.day), selected: cal.dateInSelected($data.ym,$data.day)}"> | |
<a data-bind="text: $data.day, attr: {onclick: 'selectDate('+$data.ym+','+$data.day+')'}"></a> | |
</td> | |
<!-- /ko --> | |
<!-- /ko --> | |
</tr> | |
</tbody> | |
</table> | |
</div> | |
</body> | |
<script> | |
class Calendar { | |
constructor() { | |
this.weekNames = ["Sun", "Mon", "Tue", "Wed", "Thu", "Fri", "Sat"] | |
this.availableDates = [] | |
this.selectedDatesBeginEnd = [] | |
this.selectedDatesRange = [] | |
this.selectedDatesUnavailable = [] | |
this.selectMaxRange = 100 | |
} | |
set(year, month) { | |
this.year = year | |
this.month = month - 1 | |
this.days = [] | |
this.daysByWeek = [] | |
this.beginWeekDay = moment([this.year, this.month]).startOf('month').day() | |
this.endWeekDay = moment([this.year, this.month]).endOf('month').day() | |
this.lastDayOfLastMonth = parseInt(moment([this.year, this.month]).subtract(1, 'month').endOf('month').format("DD")) | |
this.daysInMonth = moment([this.year, this.month]).daysInMonth() | |
this.createDays() | |
this.createWeekDays() | |
} | |
createDays() { | |
const lastMonth = parseInt(moment([this.year, this.month]).subtract(1, 'month').format("YYYYMM")) | |
for (let i = 1; i <= this.beginWeekDay; i++) { | |
const dayInfo = { | |
ym: lastMonth, | |
day: this.lastDayOfLastMonth - (this.beginWeekDay - i), | |
} | |
this.days.push(dayInfo) | |
} | |
const thisMonth = parseInt(moment([this.year, this.month]).format("YYYYMM")) | |
for (let i = 1; i <= this.daysInMonth; i++) { | |
const dayInfo = { | |
ym: thisMonth, | |
day: i, | |
} | |
this.days.push(dayInfo) | |
} | |
const nextMonth = parseInt(moment([this.year, this.month]).add(1, 'month').format("YYYYMM")) | |
for (let i = 1; i <= (6 - this.endWeekDay); i++) { | |
const dayInfo = { | |
ym: nextMonth, | |
day: i, | |
} | |
this.days.push(dayInfo) | |
} | |
} | |
createWeekDays() { | |
let weekdays = [] | |
for (let i = 0; i < this.days.length; i++) { | |
weekdays.push(this.days[i]) | |
if (i % 7 == 6) { | |
this.daysByWeek.push(weekdays) | |
weekdays = [] | |
} | |
} | |
} | |
getWeekDay(day) { return this.weekNames[moment([this.year, this.month, day]).day()] } | |
selectDate(ym, day) { | |
if (this.selectedDatesBeginEnd.length > 1) { | |
this.selectedDatesBeginEnd = [] | |
this.selectedDatesRange = [] | |
this.selectedDatesUnavailable = [] | |
} | |
this.selectedDatesBeginEnd.push(moment(ym + String(day).padStart(2, "0"), "YYYYMMDD").format("YYYY-MM-DD")) | |
if (this.selectedDatesBeginEnd.length == 2) { | |
const fromDate = moment(this.selectedDatesBeginEnd[0]) | |
const toDate = moment(this.selectedDatesBeginEnd[1]) | |
const diff = toDate.diff(fromDate, 'days') | |
if (diff > (this.selectMaxRange - 1)) { | |
this.selectedDatesBeginEnd = [] | |
this.selectedDatesRange = [] | |
this.selectedDatesUnavailable = [] | |
// console.log("Current range: " + diff + ", " + "Max range: " + (this.selectMaxRange - 1)) | |
return false | |
} | |
for (let i = 0; i <= diff; i++) { | |
if (!this.availableDates.includes(moment(fromDate).add(i, 'days').format("YYYY-MM-DD"))) { | |
this.selectedDatesUnavailable.push(moment(fromDate).add(i, 'days').format("YYYY-MM-DD")) | |
} | |
this.selectedDatesRange.push(moment(this.selectedDatesBeginEnd[0]).add(i, 'days').format("YYYY-MM-DD")) | |
} | |
} | |
return true | |
} | |
getDate(ym, day) { return (moment(ym + String(day).padStart(2, "0"), "YYYYMMDD").format("YYYY-MM-DD")) } | |
getYear() { return moment([this.year, this.month]).format("YYYY") } | |
getMonth() { return moment([this.year, this.month]).format("MM") } | |
setPrevMonth() { | |
this.year = moment([this.year, this.month]).subtract(1, 'month').format("YYYY") | |
this.month = moment([this.year, this.month]).subtract(1, 'month').format("MM") | |
this.set(this.year, this.month) | |
} | |
setNextMonth() { | |
this.year = moment([this.year, this.month]).add(1, 'month').format("YYYY") | |
this.month = moment([this.year, this.month]).add(1, 'month').format("MM") | |
this.set(this.year, this.month) | |
} | |
dateAvailable(date) { | |
let result = false | |
const ymd = this.getDate(date.ym, date.day) | |
if (this.availableDates.includes(ymd)) { | |
result = true | |
} | |
return result | |
} | |
dateInSelected(ym, day) { | |
let result = false | |
const ymd = this.getDate(ym, day) | |
if (this.selectedDatesRange.includes(ymd)) { | |
result = true | |
} | |
return result | |
} | |
isToday(ym, day) { | |
let result = false | |
const ymd = this.getDate(ym, day) | |
if (moment().format("YYYY-MM-DD") == ymd && !this.dateInSelected(ym, day)) { | |
result = true | |
} | |
return result | |
} | |
} | |
function moveThisMonth() { | |
cal.set(parseInt(now.format("YYYY")), parseInt(now.format("MM"))) | |
ymOBJ(cal.getYear() + " \267 " + cal.getMonth()) | |
daysByWeekOBJ(cal.daysByWeek) | |
} | |
function movePrevMonth() { | |
cal.setPrevMonth() | |
ymOBJ(cal.getYear() + " \267 " + cal.getMonth()) | |
daysByWeekOBJ(cal.daysByWeek) | |
} | |
function moveNextMonth() { | |
cal.setNextMonth() | |
ymOBJ(cal.getYear() + " \267 " + cal.getMonth()) | |
daysByWeekOBJ(cal.daysByWeek) | |
} | |
function selectDate(ym, day) { | |
const result = cal.selectDate(ym, day) | |
if (!result) { | |
alert("Possible range up to " + cal.selectMaxRange) | |
return false | |
} | |
cal.set(cal.getYear(), cal.getMonth()) | |
daysByWeekOBJ(cal.daysByWeek) | |
} | |
function resetSelection() { | |
cal.selectedDatesBeginEnd = [] | |
cal.selectedDatesRange = [] | |
cal.selectedDatesUnavailable = [] | |
cal.set(cal.getYear(), cal.getMonth()) | |
daysByWeekOBJ(cal.daysByWeek) | |
} | |
const cal = new Calendar() | |
const now = moment() | |
// Max 7 days range include unavailable dates | |
cal.selectMaxRange = 7 | |
// Possible dates YYYY-MM-DD | |
// cal.availableDates = ["2022-01-22", "2022-01-23"] | |
for (i = 0; i <= 30; i++) { | |
if (i != 7 && i != 8) { | |
cal.availableDates.push(moment().add(i, 'days').format("YYYY-MM-DD")) | |
} | |
} | |
// Change to week names | |
cal.weekNames = ["Su", "Mo", "Tu", "We", "Th", "Fr", "Sa"] | |
cal.set(parseInt(now.format("YYYY")), parseInt(now.format("MM"))) | |
// console.log(cal.getYear(), cal.getMonth(), cal.days, cal.daysByWeek) | |
const ymOBJ = ko.observable(cal.getYear() + " \267 " + cal.getMonth()) | |
const weekNamesOBJ = ko.observableArray(cal.weekNames) | |
const daysByWeekOBJ = ko.observableArray(cal.daysByWeek) | |
ko.applyBindings({ | |
ym: ymOBJ, | |
weekNames: weekNamesOBJ, | |
daysByWeek: daysByWeekOBJ, | |
}) | |
</script> | |
<style> | |
html, | |
body { | |
width: 99%; | |
min-height: 80%; | |
} | |
.cal-cavity { | |
margin-top: 30px; | |
} | |
.year-month { | |
width: 100%; | |
text-align: center; | |
border-top: 1px solid #000; | |
} | |
.calendar { | |
float: left; | |
width: 49%; | |
} | |
.cal-table { | |
width: 100%; | |
} | |
.cal-table thead th { | |
width: 14.2857142857%; | |
border-top: 1px solid #ececec; | |
border-bottom: 1px solid #ececec; | |
background: #83d386; | |
font-size: 13px; | |
font-weight: 500; | |
text-align: center; | |
line-height: 24px; | |
} | |
.cal-table .sun, | |
.cal-table .sun a { | |
color: #eb160c; | |
} | |
.cal-table .sat, | |
.cal-table .sat a { | |
color: #005bac; | |
} | |
.cal-table tbody td { | |
border-bottom: 0px solid #ececec; | |
border-right: 0px solid #ececec; | |
font-size: 14px; | |
color: #222; | |
text-align: center; | |
line-height: 65px; | |
} | |
.cal-table tbody td:first-child { | |
border-left: 0px solid #ececec; | |
} | |
.cal-table tbody td a { | |
display: block; | |
color: #222; | |
} | |
.cal-table tbody td.none a { | |
opacity: 0.3; | |
filter: alpha(opacity=30); | |
-mox-opacity: 0.3; | |
} | |
.cal-table tbody td.on a, | |
.cal-table tbody td:hover a { | |
background: #bdac16; | |
color: #fff; | |
} | |
.cal-table tbody td.selected a { | |
background: #c23c3c; | |
color: #fff; | |
} | |
.cal-table tbody td.today a { | |
background: #606060; | |
color: #fff; | |
} | |
.cal-table tbody td.today:hover a { | |
background: #373760; | |
color: #fff; | |
} | |
.cal-table tbody td.none:hover a { | |
background: inherit; | |
color: inherit; | |
} | |
</style> | |
</html> |
Sign up for free
to join this conversation on GitHub.
Already have an account?
Sign in to comment