JSでリンク入りカレンダー作ってみた

# はじめに
自前のサービス内でカレンダーの日付にリンクを貼り付けたかったのですが、良さげなものがすぐ見つからなかったので軽い気持ちで書いてみました。(ほしかったリンクは、ベースのURLにYYYY-MM-DDが入っているものです）


# 作ったもの

## 画像
![スクリーンショット 2024-03-06 23.19.59.png](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/671049/0791ba43-c778-cc36-775f-76aaa9192df4.png)

## ディレクトリ構成

any_directory
 ∟ index.html
 ∟ calendar.js
 ∟ calendar.css
 ∟ reset.css (お好きなものをお使いください)

## コード
### index.html
```html
<!DOCTYPE html>
<html lang="ja">

<head>
    <meta charset="UTF-8">
    <title>Calendar Sample</title>
    <link rel="stylesheet" type="text/css" href="./reset.css">
    <link rel="stylesheet" type="text/css" href="./calendar.css">
    <script src="./calendar.js"></script>
</head>

<body>
    <div class="month-year-label" style="font-size: x-large;">
        <span id="calendar_year"></span>年
        <span id="calendar_month"></span>月のカレンダー
    </div>
    <div class="year-sending-area" style="font-size: large;">
        <div id='prev_year' class='prev-year calendar-sending'>＜＜</div>
        <div>　　年移動　　</div>
        <div id='next_year' class='next-year calendar-sending'>＞＞</div>
    </div>
    <div class="month-sending-area" style="font-size: large;">
        <div id='prev_month' class='prev-month calendar-sending'>＜＜</div>
        <div>　　月移動　　</div>
        <div id='next_month' class='next-month calendar-sending'>＞＞
        </div>
    </div>
    <div id="calendar" class="calendar-area"></div>
</body>

<script>
    // ベースURL
    const calendarBaseUrl = "https://any_url_address.com/";
    // 年月の宣言と初期値設定
    let currentYear = new Date().getFullYear(),
        currentMonth = new Date().getMonth() + 1;
    // 週配列の作成とテーブル用のヘッダ作成
    const weekDays = ["日", "月", "火", "水", "木", "金", "土"],
        calendarHeader = `<tr><th>${weekDays.join('</th><th>')}</th></tr>`;

    // 初期のカレンダ作成 (calendar.js内のカレンダー作成関数実行)
    createCalendar();

    // イベントリスナを設定
    document.getElementById('prev_month').addEventListener('click', ()=> {
        // カレンダーを1ヶ月戻す
        if (currentMonth == 1) {
            currentMonth = 12;
            currentYear = currentYear - 1;
        } else {
            currentMonth = currentMonth - 1;
        }
        // カレンダーの再作成
        createCalendar();
    })
    document.getElementById('next_month').addEventListener('click' ,() => {
        // カレンダーを1ヶ月進める
        if (currentMonth == 12) {
            currentMonth = 1;
            currentYear = currentYear + 1;
        } else {
            currentMonth = currentMonth + 1;
        }
        // カレンダーの再作成
        createCalendar();
    })
    document.getElementById('prev_year').addEventListener('click', ()=> {
        // カレンダーを1年戻す
        currentYear = currentYear - 1;
        // カレンダーの再作成
        createCalendar();
    })
    document.getElementById('next_year').addEventListener('click' ,() => {
        // カレンダーを1年進める
        currentYear = currentYear + 1;
        // カレンダーの再作成
        createCalendar();
    })
</script>
</html>
```
### calendar.js
```javascript
function createCalendar() {

    // カレンダー上部の年月を更新
    const calendarYearLabelElem = document.getElementById("calendar_year"),
        calendarMonthLabelElem = document.getElementById("calendar_month");
    calendarYearLabelElem.innerText = currentYear;
    calendarMonthLabelElem.innerText = currentMonth;

    // calendar要素
    const calendarElem = document.getElementById("calendar");
    const calendarTableElem = document.getElementById("table_calendar");

    // 存在する場合は削除。(削除後に新規作成する)
    if (calendarTableElem) {
        calendarTableElem.remove();
    }

    // 月の初めと最終日を取得
    const firstDate = new Date(currentYear, currentMonth-1, 1),
        firstDateWeekDay = firstDate.getDay(),
        monthLastDate = new Date(currentYear, currentMonth, 1 - 1);
    let loopDate = new Date(currentYear, currentMonth-1, 1 - firstDateWeekDay);

    // テーブル要素を作成
    const tableElem = document.createElement('table'),
        theadElem = document.createElement('thead'),
        tbodyElem = document.createElement('tbody');
    tableElem.setAttribute('id', 'table_calendar');
    tableElem.setAttribute('class', 'table-calendar w-50');

    // tbody > tr要素を作成
    for(i=0; (loopDate <= monthLastDate); i++) {
        const trElem = document.createElement('tr');
        for(j=0; j<7 ; j++) {
            tdElem = document.createElement('td');
            if (j == 0 && firstDate <= loopDate && loopDate <= monthLastDate) {
                tdElem.setAttribute('style', 'color: red;')
            } else if (j==6 && firstDate <= loopDate && loopDate <= monthLastDate) {
                tdElem.setAttribute('style', 'color: blue;')
            }
            const loopYearStr = loopDate.getFullYear(),
                loopMonthStr = ('0'+(loopDate.getMonth() + 1).toString()).slice(-2),
                loopDateStr = ('0'+(loopDate.getDate()).toString()).slice(-2);
            const dateStr = `${loopYearStr}-${loopMonthStr}-${loopDateStr}`,
                linkUrl = `${calendarBaseUrl}${dateStr}`,
                aTag = `<a href="${linkUrl}">${loopDate.getDate()}</a>`;
            if ( firstDate <= loopDate && loopDate <= monthLastDate ) {
                tdElem.innerHTML = aTag;
            } else {
                tdElem.innerHTML = `<span style="color: gray;">${aTag}</span>`;
            }
            trElem.appendChild(tdElem)
            loopDate.setDate(loopDate.getDate() + 1);
        }
        tbodyElem.appendChild(trElem)
    }

    theadElem.innerHTML = calendarHeader;
    tableElem.appendChild(theadElem);
    tableElem.appendChild(tbodyElem);
    calendarElem.appendChild(tableElem);
}
```

### calendar.css
```css
.month-year-label {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-bottom: 1em;
}

.year-sending-area {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-bottom: 1em;
}

.month-sending-area {
    display: flex;
    flex-direction: row;
    justify-content: center;
    align-items: center;
    margin-bottom: 1em;
}

.calendar-sending {
    cursor: pointer;
}

.calendar-area {
    width: 100%;
    display: flex;
    flex-direction: row;
    justify-content: center;
}

.calendar-area table {
    border-collapse: collapse;
}

.table-calendar {
    width: 50%;
}

.table-calendar a {
    color: inherit;
    text-decoration: none
}

.table-calendar th, td {
    font-size: x-large;
    border: solid 1px black;
    text-align: center;
}

.table-calendar > thead > tr > th {
    height: 35px;
    background-color: lightblue;
}

.table-calendar > tbody > tr > td {
    height: 45px;
    background-color: whitesmoke;
}

.table-calendar > tbody > tr > td:hover {
    background-color: lightyellow;
}
```

# 最後に
軽い気持ちで書いてみましたが、3時間くらい掛けてしまいました...
最近JS書いてなかったのでメソッドを忘れたりして結構時間ロスが結構ありました。
イメージができていたのでもう1時間くらい早く作りたかったなぁ、とも思っています。
とはいえ、作り方を知っていただけだったのが、作った経験があるにランクアップできたのは個人的にうれしかったです。
（自己満足の記事です）