<!DOCTYPE html>
<meta charset="utf-8">
<title></title>
<style>
table {
margin: 1em 0;
}
thead td {
font-weight: bold;
}
thead td, tbody td {
text-align: center;
border: 1px transparent solid;
}
tbody td:hover:not(.pDay):not(.nDay) {
border: 1px #f80 solid;
}
tr td:first-of-type {
color: red;
}
tr td:last-of-type {
color:#099;
}
thead td { padding: 0 .5ex;}
caption label {
padding: 0 .5ex;
font-size: large;
font-weight: bold;
}
caption button {
background: transparent;
border-style: none;
font-size: large;
padding: 0;
}
caption .hide {
display: none;
}
tbody td.pDay, tbody td.nDay {
color: silver;
font-size: small;
vertical-align: top;
}
tbody td.nDay {
color: silver;
font-size: small;
vertical-align: bottom;
}
</style>
<table></table>
<table></table>
<table></table>
<script>
class Calendar {
constructor (table, date, option = { }) {
this.table = table;
this.date = date;
this.option = Object.assign ({ }, this.constructor.defaultOption (), option);
this.view ();
}
view () {
const
{table, option, constructor: c } = this,
{remove, getYMD, setNo, splice, toTBody, append} = c.tools ();
let
tbody = document.createElement ('tbody'),
[y, m] = getYMD (this.current),
[,, pd, pw] = getYMD (new Date (y, m, 0)),
[,, nd, nw] = getYMD (new Date (y, m + 1, 0)),
pn = (pw + 1) % 7,
nn = 6 - nw,
days = [
...setNo (pn, pd - pn + 1),
...setNo (nd),
...setNo (nn)
];
remove (table.children);
append (table.createCaption (), option.caption);
toTBody (table.createTHead (), option.weekName);
toTBody (table.appendChild (tbody), [...splice (days, 7)]);
[...table.querySelectorAll ('caption label')]
.forEach ((e, i)=> e.textContent = [y, option.monthName[m]][i]);
let es = [...table.querySelectorAll ('tbody td')];
if (pn) es.slice (0, pn).forEach (e=> e.classList.add ('pDay'));
if (nn) es.slice (-nn).forEach (e=> e.classList.add ('nDay'));
return this;
}
offsetMonth (n = 0) {
this.current.setMonth (this.current.getMonth () + n);
this.view ();
return this;
}
set date (dt) {
const {getYMD} = this.constructor.tools ();
let [y, m] = getYMD (dt);
this.current = new Date (y, m, 1);
this._date = dt;
}
getDate (td) {
const {zp, getYMD} = this.constructor.tools ();
let
[y, m, _, w] = getYMD (this.current),
d = Number (td.textContent),
ymd = [y, m+1, d],
str = ymd.map ((a,b)=>zp(a,[4,2,2][b])).join ('-');
return [new Date (y, m, d), str, ...ymd, w];
}
event (td) {
if (td) {
let
cbFunc = this.option.cbFunc,
args = this.getDate (td);
if ('function' === typeof cbFunc)
cbFunc.apply (this, args);
if (this.option.clipboard)
navigator.clipboard.writeText (args[1]).then(()=> console.log (args));
}
}
handleEvent (event) {
let e = event.target, p;
switch (n.nodeName) {
case 'BUTTON' :
if (p = e.closest ('caption')) {
let btNo = [...c.querySelectorAll('button')].indexOf (e);
return this.offsetMonth ([-12, -1, 1, 12][btNo]);
}
break;
case 'TD' :
if (p = e.closest ('tbody'))
return this.event (e);
break;
}
}
static tools () {
return {
zp: (a,b=2)=>String(a).padStart(b,'0'),
remove: a=>[...a].map(a=>a.remove()),
getYMD: a=>['FullYear','Month','Date','Day'].map(b=>a['get'+b]()),
setNo: function*(a,b=1,c=1,d=0){for(;d<a;d+=c)yield b+d},
splice: function*(a,b=1){while(a.length)yield a.splice(0,b)},
append: ((f=(a,b,c,{tag:T,child:C,...O}=b)=>Array.isArray(b)?b.reduce((a,b)=>f(a,b),a):(Object.assign(a.appendChild(c=document.createElement(T)),O),C&&f(c,C),a))=>f)(),
toTBody: (a,ary)=>{
const
reg = /^(#?)(?:\[(\d+)?(?:\,(\d+)?)?\])?\s*(?:(.+)\s)*\s*(?:([+-]?(?:[1-9][0-9]{0,2}(?:\,?[0-9]{3})*)?(?:0?(?:\.\d*))?)|(.+))?$/,
setAttr = (a,b,O=Object)=>O.assign(a,O.fromEntries(O.entries(b).filter(c=>'undefined'!==typeof c[1])));
for (let row of ary) {
let tr = a.insertRow ();
for (let cell of row) {
let
[,thd, colSpan, rowSpan, className, num, text] = reg.exec (cell),
td = tr.appendChild (document.createElement (thd ? 'th': 'td')),
attr = {colSpan, rowSpan, className, textContent: text || num || ''};
if (num != null)
className += 'num';
setAttr (td, attr);
tr.appendChild (td);
}
}
}
};
}
static defaultOption () {
return {
weekName: [['Sun','Mon','Tue','Wed','Thu','Fri','Sat']],
monthName: ['January','February','March','April','May','June','July','August','September','October','November','December'],
caption: [
{ tag: 'button', type: 'button', textContent: '⏪', className: 'hide' },
{ tag: 'button', type: 'button', textContent: '<' },
{ tag: 'label', className: 'hide' },
{ tag: 'label'},
{ tag: 'button', type: 'button', textContent: '>'},
{ tag: 'button', type: 'button', textContent: '⏩', className: 'hide' }
],
cbFunc: null,
clipboard: true,
};
}
static create (table = document.createElement ('table'), date = new Date, option = { }) {
const calendar = new this (table, date, option);
table.addEventListener ('click', calendar, false);
return calendar;
}
}
const
TABLE = document.querySelectorAll ('table'),
[a, b, c] = Array.from (TABLE, t=> Calendar.create (t));
b.offsetMonth (+1);
c.offsetMonth (+2);
</script>