1.目的
LWC での DatePicker の作成方法を共有します。
2.ソース構成図
lwc
├─datePicker
└─datePickerContainer
.select-box {
background-color: rgb(255, 255, 255);
border: 1px solid rgb(192, 192, 192);
border-radius: 0.25rem;
transition: border 0.1s linear, background-color 0.1s linear;
height: calc(1.875rem + (1px * 2));
}
.select-box[disabled] {
background-color: rgb(233, 234, 236);
border-color: rgb(196, 198, 202);
cursor: not-allowed;
-webkit-user-select: none;
-moz-user-select: none;
-ms-user-select: none;
user-select: none;
}
.inpS {
width: 70%;
margin: 0 0.5rem 0 0;
text-align:center;
text-align-last:center;
}
.error-message {
color: var(--lwc-colorTextError, rgb(194, 57, 52));
}
.select-has-error {
background-color: var(--lwc-colorBackgroundInput, rgb(255, 255, 255));
border-color: var(--lwc-colorBorderError, rgb(194, 57, 52));
box-shadow: var(--lwc-colorBorderError, rgb(194, 57, 52)) 0 0 0 var(--lwc-borderWidthThin, 1px) inset;
background-clip: padding-box;
}
<template>
<div class="slds-form-element">
<label class="slds-form-element__label" data-id="label">{label}</label>
<div class="slds-form-element__control">
<div class="slds-grid slds-form_horizontal">
<div class="slds-col slds-col slds-size_1-of-3 slds-small-size_1-of-3 slds-medium-size_1-of-3">
<select class="select-box inpS" data-id="year" disabled={getdisable} onchange={yearBoxChange}
required={getRequired}>
</select>
年
</div>
<div class="slds-col slds-col slds-size_1-of-3 slds-small-size_1-of-3 slds-medium-size_1-of-3">
<select class="select-box inpS" data-id="month" disabled={getdisable} onchange={monthBoxChange}
required={getRequired}>
</select>
月
</div>
<div class="slds-col slds-col slds-size_1-of-3 slds-small-size_1-of-3 slds-medium-size_1-of-3">
<select class="select-box inpS" data-id="day" disabled={getdisable} onchange={dateBoxChange}
required={getRequired}>
</select>
日
</div>
</div>
</div>
<div data-help-message="true" role="alert" class="error-message"></div>
</div>
</template>
import { LightningElement, api, track } from 'lwc';
export default class datePicker extends LightningElement {
//開始年
@api startYear = 1901;
//ラベル
@api label;
//可用
@api disabled = false;
//必須
@api required = false;
//年
@track yearVal;
//月
@track monthVal;
//日
@track dayVal;
//yearElement
yearBox;
//monthElement
monthBox;
//dateElement
dateBox;
// 日付データ
today = new Date();
thisYear = this.today.getFullYear();
thisMonth = this.today.getMonth() + 1;
thisDate = this.today.getDate();
datesOfYear = [31, this.countDatesOfFeb(this.thisYear), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
/**
* 年月日の取得
* @return {string} YYYY/MM/DD
*/
@api
get ymdval() {
this.yearVal = this.yearBox.childNodes[this.yearBox.selectedIndex].value;
this.monthVal = this.monthBox.childNodes[this.monthBox.selectedIndex].value;
this.dayVal = this.dateBox.childNodes[this.dateBox.selectedIndex].value;
let ymd = `${this.yearVal}/${this.monthVal}/${this.dayVal}`;
if(ymd.length === 10)
return ymd;
return '';
}
/**
* 年月日の設定
* @param {string} val YYYY/MM/DDまたはYYYY-MM-DD
*/
set ymdval(val) {
if (val && val.length === 10) {
this.yearVal = val.substring(0, 4);
this.monthVal = val.substring(5, 7);
this.dayVal = val.substring(8, 10);
} else {
this.yearVal = '';
this.monthVal = '';
this.dayVal = '';
}
if (this.yearVal && this.monthVal && this.dayVal &&
this.yearBox && this.monthBox && this.dateBox) {
this.yearBox.innerHTML = '';//年クリア
this.monthBox.innerHTML = '';//月クリア
this.dateBox.innerHTML = '';//日クリア
this.datesOfYear = [31, this.countDatesOfFeb(this.yearVal), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
this.createOption(this.yearBox, this.startYear, this.thisYear, this.yearVal);//年の設定
this.createOption(this.monthBox, 1, 12, this.monthVal);//月の設定
this.createOption(this.dateBox, 1, this.datesOfYear[this.monthVal - 1], this.dayVal);//日の設定
}
}
/**
* 可用
*/
get getdisable() {
return this.disabled === 'true' || this.disabled === true;
}
/**
* 必須
*/
get getRequired() {
return this.required === 'true' || this.required === true;
}
/**
* 親から初期化
*/
renderedCallback() {
// console.log('<=======DatePickerDebug=========>');
this.yearBox = this.template.querySelector('select[data-id="year"]');
this.monthBox = this.template.querySelector('select[data-id="month"]');
this.dateBox = this.template.querySelector('select[data-id="day"]');
// 初期値を設定
if (!(this.yearBox.innerHTML && this.monthBox.innerHTML && this.dateBox.innerHTML)){
this.createOption(this.yearBox, this.startYear, this.thisYear, this.yearVal);//年の設定
this.createOption(this.monthBox, 1, 12, this.monthVal);//月の設定
this.createOption(this.dateBox, 1, this.datesOfYear[this.monthVal - 1], this.dayVal);//日の設定
}
let labelElement = this.template.querySelector('label[data-id="label"]');
if (this.required === 'true' || this.required === true) {
labelElement.innerHTML = `<abbr lightning-input_input="" title="必須" class="slds-required">*</abbr> ${this.label}`;
}else{
labelElement.innerHTML = this.label;
}
}
// 年イベント
yearBoxChange(e) {
// this.monthBox.innerHTML = '';//月クリア
this.yearVal = e.target.value;
this.monthVal = this.monthBox.childNodes[this.monthBox.selectedIndex].value;
this.dayVal = this.dateBox.childNodes[this.dateBox.selectedIndex].value;
this.datesOfYear = [31, this.countDatesOfFeb(this.yearVal), 31, 30, 31, 30, 31, 31, 30, 31, 30, 31];
// this.createOption(this.monthBox, 1, 12, this.monthVal);
this.dateBox.innerHTML = '';//日クリア
this.createOption(this.dateBox, 1, this.datesOfYear[this.monthVal ? this.monthVal - 1 : this.thisMonth -1], this.dayVal);
const changenEvent = new CustomEvent('change', {
detail: this.ymdval
});
this.dispatchEvent(changenEvent);
this.checkValidity();
}
// 月イベント
monthBoxChange(e) {
this.monthVal = e.target.value;
this.yearVal = this.yearBox.childNodes[this.yearBox.selectedIndex].value;
this.dayVal = this.dateBox.childNodes[this.dateBox.selectedIndex].value;
this.dateBox.innerHTML = '';//日クリア
this.createOption(this.dateBox, 1, this.datesOfYear[this.monthVal ? this.monthVal - 1 : this.thisMonth -1], this.dayVal);
const changenEvent = new CustomEvent('change', {
detail: this.ymdval
});
this.dispatchEvent(changenEvent);
this.checkValidity();
}
// 日イベント
dateBoxChange(e) {
this.dayVal = e.target.value;
const changenEvent = new CustomEvent('change', {
detail: this.ymdval
});
this.dispatchEvent(changenEvent);
this.checkValidity();
}
// ライブラリ
/**
* 任意の年が閏年であるかをチェックする
* @param {number} year チェックしたい西暦年号
* @return {boolean} 閏年であるかを示す真偽値
*/
isLeapYear(year) {
return (year % 4 === 0) && (year % 100 !== 0) || (year % 400 === 0);
}
/**
* 任意の年の2月の日数を数える
* @param {number} year チェックしたい西暦年号
* @return {number} その年の2月の日数
*/
countDatesOfFeb(year) {
return this.isLeapYear(year) ? 29 : 28;
}
/**
* セレクトボックスの中にオプションを生成する
* @param {string} dom セレクトボックスのDOMのid属性値
* @param {number} startNum オプションを生成する最初の数値
* @param {number} endNum オプションを生成する最後の数値
* @param {string} current 現在の日付にマッチする数値
*/
createOption(dom, startNum, endNum, current) {
let blankOption = document.createElement('option');
dom.appendChild(blankOption);
for (let j = startNum; j <= endNum; j++) {
let option = document.createElement('option');
if (j === Number(current)) {
option.value = this.paddingFormat(j);
option.innerHTML = this.paddingFormat(j);
option.selected = true;
} else {
option.value = this.paddingFormat(j);
option.innerHTML = this.paddingFormat(j);
}
dom.appendChild(option);
}
}
/**
* ゼロ埋まる
* @param {string} i
*/
paddingFormat(i) {
if (i.toString().length < 2)
return '0' + i;
return i;
}
/**
* チェック結果
*/
@api
checkValidity() {
let validity = true;
let className = 'select-has-error';
let errorbar = this.template.querySelector('div[class = "error-message"]');
if (!this.disabled && this.required && (!this.ymdval || this.ymdval.length != 10)) {
errorbar.innerHTML = 'この項目を選択してください。'
this.yearBox.classList.add(className);
this.monthBox.classList.add(className);
this.dateBox.classList.add(className);
validity = false;
} else {
errorbar.innerHTML = '';
this.yearBox.classList.remove(className);
this.monthBox.classList.remove(className);
this.dateBox.classList.remove(className);
validity = true;
}
return validity;
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<isExposed>false</isExposed>
</LightningComponentBundle>
<template>
<div class="slds-card" style="height:500px;width:1200px;">
<div>{dateStr}</div>
<div class="slds-col slds-size_3-of-12">
<c-date-picker label="年月日:" required={required} onchange={dateChange}></c-date-picker>
</div>
<lightning-button label="内容チェック" onclick={dateCheckHandler}></lightning-button>
</div>
</template>
import { LightningElement, track } from 'lwc';
export default class DatePickerContainer extends LightningElement {
@track dateStr;
@track required = true;
/**
* 日付選択
* @param {*} e
*/
dateChange(e) {
e.preventDefault();
this.dateStr = e.detail;
}
/**
* 日付チェック
* @param {*} e
*/
dateCheckHandler(e) {
e.preventDefault();
[...this.template.querySelectorAll('c-date-picker')].reduce((previousValue, currentValue) => {
return previousValue && currentValue.checkValidity();
}, true)
}
}
<?xml version="1.0" encoding="UTF-8"?>
<LightningComponentBundle xmlns="http://soap.sforce.com/2006/04/metadata">
<apiVersion>52.0</apiVersion>
<isExposed>false</isExposed>
</LightningComponentBundle>
3.ロカールで動作確認
datePickerContainer 中に右クリックし、SFDX:Preview Component Locally
を押下する
Use Desktop Browser
を選択する
サーバを立ち上げて、ブラウザを自動的に開く