SalesforceのLWCでカスタムCalendarコンポーネントを作る方法を紹介します。
https://www.lightningdesignsystem.com/components/datepickers/
上記公式サイトのUIを基づいて、LWCでカスタムCalendarコンポーネントを作成する例です。
.noclick {
pointer-events: none;
}
.day {
cursor: pointer;
}
<template>
<div class="slds-datepicker slds-dropdown" role="dialog">
<div class="slds-datepicker__filter slds-grid">
<div
class="slds-datepicker__filter_month slds-grid slds-grid_align-spread slds-grow"
>
<div class="slds-align-middle">
<button
class="slds-button slds-button_icon slds-button_icon-container"
title="Previous Month"
onclick="{prev}"
>
<lightning-icon icon-name="utility:left" size="x-small">
</lightning-icon>
<span class="slds-assistive-text">Previous Month</span>
</button>
</div>
<h2 class="slds-align-middle">{currentMonth}月</h2>
<div class="slds-align-middle">
<button
class="slds-button slds-button_icon slds-button_icon-container"
title="Next Month"
onclick="{next}"
>
<lightning-icon icon-name="utility:right" size="x-small">
</lightning-icon>
<span class="slds-assistive-text">Next Month</span>
</button>
</div>
</div>
<div class="slds-shrink-none">
<div class="slds-select_container">
<select class="slds-select" onchange="{yearSelectChange}">
<template
for:each="{selectYearList}"
for:item="item"
for:index="index"
>
<template if:true="{item.selected}">
<option key="{item.value}" value="{item.value}" selected>
{item.value}
</option>
</template>
<template if:false="{item.selected}">
<option key="{item.value}" value="{item.value}">
{item.value}
</option>
</template>
</template>
</select>
</div>
</div>
</div>
<table class="slds-datepicker__month" role="grid">
<thead>
<tr>
<th scope="col">
<abbr title="日曜日">日</abbr>
</th>
<th scope="col">
<abbr title="月曜日">月</abbr>
</th>
<th scope="col">
<abbr title="火曜日">火</abbr>
</th>
<th scope="col">
<abbr title="水曜日">水</abbr>
</th>
<th scope="col">
<abbr title="木曜日">木</abbr>
</th>
<th scope="col">
<abbr title="金曜日">金</abbr>
</th>
<th scope="col">
<abbr title="土曜日">土</abbr>
</th>
</tr>
</thead>
<tbody>
<template for:each="{dayList}" for:item="items" for:index="index">
<tr key="{items.id}">
<template for:each="{items.value}" for:item="item" for:index="idx">
<template if:true="{item.adjacentMonth}">
<td class="slds-day_adjacent-month" key="{item.day}">
<span
class="slds-day"
data-value="{item.value}"
onclick="{dateSelectChange}"
>
{item.day}
</span>
</td>
</template>
<template if:false="{item.adjacentMonth}">
<template if:true="{item.today}">
<td class="slds-is-today" key="{item.day}">
<span
class="slds-day"
data-value="{item.value}"
onclick="{dateSelectChange}"
>
{item.day}
</span>
</td>
</template>
<template if:false="{item.today}">
<td key="{item.day}">
<span
class="slds-day"
data-value="{item.value}"
onclick="{dateSelectChange}"
>
{item.day}
</span>
</td>
</template>
</template>
</template>
</tr>
</template>
</tbody>
</table>
<button
class="slds-button slds-align_absolute-center slds-text-link"
onclick="{todayClickHandler}"
>
今日
</button>
</div>
</template>
import { LightningElement, track } from "lwc";
const DAY_OF_WEEK = ["日", "月", "火", "水", "木", "金", "土"];
export default class Calendar extends LightningElement {
//現在の日付
today = new Date();
// 月末だとずれる可能性があるため、1日固定で取得
showDate = new Date(this.today.getFullYear(), this.today.getMonth(), 1);
@track selectYearList = [];
@track dayList = [];
@track currentYear = this.showDate.getFullYear();
@track currentMonth = this.showDate.getMonth() + 1;
@track currentDay = this.showDate.getDate();
// 前の月表示
prev(e) {
e.preventDefault();
this.showDate.setMonth(this.showDate.getMonth() - 1);
this.currentMonth = this.showDate.getMonth() + 1;
this.showProcess(this.showDate);
}
// 次の月表示
next(e) {
e.preventDefault();
this.showDate.setMonth(this.showDate.getMonth() + 1);
this.currentMonth = this.showDate.getMonth() + 1;
this.showProcess(this.showDate);
}
// カレンダー表示
showProcess(date) {
let year = date.getFullYear();
let month = date.getMonth();
this.createProcess(year, month);
}
// カレンダー作成
createProcess(year, month) {
let count = 0;
let startDayOfWeek = new Date(year, month, 1).getDay();
let endDate = new Date(year, month + 1, 0).getDate();
let lastMonthEndDate = new Date(year, month, 0).getDate();
let row = Math.ceil((startDayOfWeek + endDate) / DAY_OF_WEEK.length);
this.dayList = [];
// 1行ずつ設定
for (let i = 0; i < row; i++) {
this.dayList.push({ value: [], id: i });
// 1colum単位で設定
for (let j = 0; j < DAY_OF_WEEK.length; j++) {
if (i == 0 && j < startDayOfWeek) {
// 1行目で1日まで先月の日付を設定
this.dayList[i].value.push({
adjacentMonth: true,
today: false,
day: lastMonthEndDate - startDayOfWeek + j + 1,
value: `${this.currentYear}-${this.currentMonth - 1}-${
lastMonthEndDate - startDayOfWeek + j + 1
}`,
});
} else if (count >= endDate) {
// 最終行で最終日以降、翌月の日付を設定
count++;
this.dayList[i].value.push({
adjacentMonth: true,
today: false,
day: count - endDate,
value: `${this.currentYear}-${this.currentMonth + 1}-${
count - endDate
}`,
});
} else {
// 当月の日付を曜日に照らし合わせて設定
count++;
if (
year == this.today.getFullYear() &&
month == this.today.getMonth() &&
count == this.today.getDate()
) {
this.dayList[i].value.push({
adjacentMonth: false,
today: true,
day: count,
value: `${this.currentYear}-${this.currentMonth}-${count}`,
});
} else {
this.dayList[i].value.push({
adjacentMonth: false,
today: false,
day: count,
value: `${this.currentYear}-${this.currentMonth}-${count}`,
});
}
}
}
}
}
/**
* セレクトボックスの中にオプションを生成する
* @param {number} startNum オプションを生成する最初の数値
* @param {number} endNum オプションを生成する最後の数値
* @param {string} current 現在の日付にマッチする数値
*/
createYearOption(startNum, endNum, current) {
for (let j = startNum; j <= endNum; j++) {
let selected;
if (j === Number(current)) {
selected = true;
} else {
selected = false;
}
this.selectYearList.push({
value: j,
selected: selected,
});
}
}
/**
* 年を選択
* @param {*} e
*/
yearSelectChange(e) {
e.preventDefault();
let selectedIndex = e.target.selectedIndex;
this.currentYear = e.target.options[selectedIndex].value;
let currentDate = new Date(
this.currentYear,
this.currentMonth - 1,
this.currentDay
);
this.showProcess(currentDate);
}
/**
* スタイル削除
*/
removeCurrentlySelectedDateAttributes() {
const e = this.template.querySelector("td[class*='slds-is-selected']");
e && e.classList.remove("slds-is-selected");
}
/**
* 日付を選択
* @param {*} e
*/
dateSelectChange(e) {
e.preventDefault();
let target = e.target;
this.removeCurrentlySelectedDateAttributes();
target.parentElement.classList.add("slds-is-selected");
let dateStr = target.dataset.value;
this.dispatchEvent(
new CustomEvent("select", {
detail: new Date(dateStr),
})
);
}
/**
* 今日を選択
* @param {*} e
*/
todayClickHandler(e) {
e.preventDefault();
this.showDate = new Date();
this.dispatchEvent(
new CustomEvent("select", {
detail: this.showDate,
})
);
}
connectedCallback() {
this.showProcess(this.today);
let thisYear = this.today.getFullYear();
this.createYearOption(thisYear - 100, thisYear + 100, thisYear); //年の設定
}
}
- 動作