Skip to content

Instantly share code, notes, and snippets.

@edp1096
Last active January 25, 2022 11:09
Show Gist options
  • Save edp1096/1c9b5670dc4decc00096762fbb4661d8 to your computer and use it in GitHub Desktop.
Save edp1096/1c9b5670dc4decc00096762fbb4661d8 to your computer and use it in GitHub Desktop.
Calendar using knockout.js
<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/knockout@3.5.1/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">&lt;</a>
<b data-bind="text: ym">1970&nbsp;&nbsp;&middot;&nbsp;&nbsp;13</b>
<a href="#" class="month_next" onclick="moveNextMonth(); return false">&gt;</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