履歴書の年表アプリ 2021/8/17更新

<h2>まえがき</h2>

こんにちは、今回も練習の一環で自作物を作りました。
お題は「<b>履歴書で使用する年表を計算してくれるアプリ</b>」です。
言葉で説明されてもわかりにくいと思うので、とりあえず完成品をお見せします。
![1.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/d0707541-5a6b-0a86-c56c-105a363fdb10.jpeg)
この様に生年月日を入力すると、いつ入学して、いつ卒業したのかの一覧を作ってくれる簡素なアプリです。
自分で計算するとなると面倒くさいですよね？少なくとも前回の閏年判定アプリよりは使えますね！
小学校入学・卒業なんて履歴書に書かないとは思いますが…

今回も制作の記録をここに残します。

制作時間は12時間です。



<h2>もくじ</h2>

1.[目的](#1-目的)

2.[結果](#2-結果)

3.[制作の流れ](#3-制作の流れ)

4.[ふりかえり](#4-ふりかえり)

5.[更新版](#5-更新版) 2021/8/17更新



##1. 目的

このアプリのメインである履歴年表の処理自体は非常に簡単だと思います。
生まれた年から足し算をして、その結果を画面に表示させるだけなのだから。


難しそうなのは４月２日の判定と入力制限だと思います。
特に入力制限に関して、３１日がない月と、２月の閏年は厄介そうな問題だと思いました。


ということで今回の目的は、<b>キッチリと入力制限が施された実用的なアプリを作ること</b>を目的にします。

##2. 結果

完成品は以下のような見た目です。
![2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/7e851488-8e44-a928-dcc0-fc6ab395e11d.jpeg)
![a4.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/3d424eab-4117-47a9-2076-c9ddcaa9b43b.jpeg)
<b>1: 「なし」のラジオボタンをチェックした状態で履歴年表作成を押すと高校卒業以降は表示されません。</b>
<b>2: 「専門・短大」のラジオボタンをチェックした状態で履歴年表作成を押すと専門・短大の年表が表示されます。</b>
<b>3: 「４年制大学」のラジオボタンをチェックした状態で履歴年表作成を押すと４年制大学の年表が表示されます。</b>

![a2.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/d2227d3b-fb67-6f33-8425-397e5f8ecd7f.jpeg)
<b>4: 値が入力されたいない状態で履歴年表作成を押すと、未入力であると注意されます。</b>
<b>5: 存在しない月日の状態で履歴年表作成を押すと、値が不正であると注意されます。</b>

![a3.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/d8e2279d-3758-b560-7cf8-6aa785c42799.jpeg)
<b>6: 閏年を判定します。閏年以外は2月29日を存在しない月日と判断します。</b>


##3. 制作の流れ

<b>「1.目的」でも触れましたが、このアプリで厄介そうな”４月２日の判定と入力制限（３１日がない月と、２月の閏年）”の問題を中心に話を進めます。</b>

そして、後に判明した「プルダウンで未入力だった時の判定」について最後に話したいと思います。
<br>

 [4月2日の判定](#4月2日の判定)

 [入力制限](#入力制限)

 [プルダウンで未入力だった時の判定](#プルダウンで未入力だった時の判定)

<br>
また、今回は<b>製作途中の変数名などは日本語です。これは私の英語力の問題です。
完成時には英語に直しています。</b>


## 4月2日の判定

日本では4月1日生まれか、4月2日生まれかで学年が変わります。
つまり、<b>4月1日生まれなら生年から6年を足せば小学校入学ですが、4月2日生まれなら7年を足さないといけません。</b>


<b>入力された値が4月1日よりも値が大きければ、7年を足し、小さければ6年を足す。</b>
という処理になると思います。


ということで以下のように月を100倍し、その後月と日の値を足して401より大きかどうか判定します。
（後で気が付きましたがmonthStatasの渡す値は最初から100倍にしててもよさそうですね…）

```javascript
const monthStatas = 4;
const dayStatas = 2;

const 月 = Number(monthStatas) * 100;
const 日 = Number(dayStatas);

let 月日 = 月 + 日;
let 自分の履歴;

const 履歴 = function(set){
    let 履歴配列 = [];
    if(set > 401){
        履歴配列.push(7);
        return 自分の履歴 = 履歴配列;
    }else{
        履歴配列.push(6);
        return 自分の履歴 = 履歴配列;
    }
}

console.log(履歴(月日));
```
”自分の履歴”という配列を使用しているのは、完成版では表示させる際にインデックスで指定して値を表示させたいからです。

出力結果
![ili.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/a392e9ed-9cf7-2f03-47f7-b26e594d24c9.jpeg)
ちなみに月日が401の時は以下のように出力されます。
![nmhg.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/0f99f989-b6b4-c58d-d163-8b60af292f70.jpeg)
大丈夫そうですね。




## 入力制限

３１日がない月と、２月の閏年の問題…
より具体的に考えると
2月30日、2月31日、4月31日、6月31日、9月31日、11月31日
それと閏年でない場合は2月29日、以上の7日の入力制限をする必要があります。

順番としては、<b>まず閏年の判定を行い、その後閏年ならば上記の6日を除外、閏年でない場合は上記の６日に加えて2月２９日も除外します。</b>


<h4>閏年の判定

前に書いた記事”『閏年判定アプリ』を作った”にてコメントで教えていただいた方法で判定しようと思います。

```javascript
    if( new Date(生年, 1, 29).getDate() === 29 ){
        console.log("閏年です");
    }
        console.log("閏年ではない");
```
これは<b>new Date()に変数yearと２月の２９番目を設定し、それが存在する年の場合は２９を返すのでtrueになります。</b>
他にも方法がありますので。詳しくは [閏年の判定方法について](https://qiita.com/huwg2787x/items/e553efa27e4d300cb0ad) をご参照ください。


<h4>閏年の場合、閏年でない場合の判定

閏年の場合と閏年でない場合で除外する配列を作り、includes()で判定しようと思います。

```javascript
const 閏年の時の除外日 = [230, 231, 431, 631, 931, 1131];
const 閏年でないの時の除外日 = [229, 230, 231, 431, 631, 931, 1131];
```

入力された月日が、閏年なら上の配列、閏年でなければ下の配列を参照して一致する数字列があるかどうか確認します。

```javascript
function 入力制限(生年, 月日){
    if( new Date(生年, 1, 29).getDate() === 29 ){
        if(閏年の時の除外日.includes(月日)){
            return; //閏年で、なおかつ存在しない月日
        }else{
            return; //閏年で、存在する月日
        }
    }else{
        if(閏年でないの時の除外日.includes(月日)){
            return; //閏年でなく、なおかつ存在しない月日       
        }else{
            return; //閏年でなく、存在する月日
        }       
    }
}
```

この判定が正しいのかどうかを1990年～2000年にかけて境界値分析を行い、問題ないことを確認しました。


## プルダウンで未入力だった時の判定

今回、プルダウンメニューを使用しており、見栄えの関係上、一番上の数値はハイフンにしています。
![hrt.jpg](https://qiita-image-store.s3.ap-northeast-1.amazonaws.com/0/1820236/b2152ef1-2019-9dea-050e-9adc5ac8897d.jpeg)
そして、<b>このハイフンを選択した状態でボタンを押された時
「注意！未入力の箇所があります。」と表示されるようにしたいと思います。</b>

一番簡単だと思ったのは、<b>ハイフンの箇所に設定する値(value)をInfinityにする</b>という方法です。

```html
 19<select name="num" id = "pulldownYear">
                <option value=Infinity>-</option><!--年-->
                        <!--以下中略-->

                <select name="month" id = "pulldownMonth"><!--月-->
                    <option value=Infinity>-</option>
                        <!--以下中略-->

                    <select name="day" id = "pulldownDay"><!--日-->
                        <option value=Infinity>-</option>
                        <!--以下省略-->
```
こうすることで、JavaScriptで以下のような判定ができます。

```javascript
if(!(生年 + 月 + 日 === Infinity)){}
```

この様な強引な判定ができます。3つの値のどれか一つでもInfinityが含まれていたらInfinityになります。
ORを使うほうが正しいと思いますが…

##4. ふりかえり

今回は「3.制作の流れ」にて触れてはいませんでしたが、try{}catch{}を使うなど色々教科書に書かれていたことを実際に導入することができて楽しかったです。制作の目的としていた<b>キッチリと入力制限が施された実用的なアプリを作ること</b>は満足いく結果でした。次はオブジェクト指向を用いた、アプリケーションの自作に挑戦してみたいです。

ここまで読んでいただきありがとうございました。
最後に、(CSSは特に面白いことはしてないので…）HTMLとJavaScriptを公開します。
もっと綺麗なコードが書けるよう精進していきます。

```html
<!DOCTYPE html>
<html>
    <head>
        <meta charset = "UTF-8">
        <title>自分の履歴年表作成アプリ</title>
        <link rel = "stylesheet" href = "styles.css">
    </head>
    <body>

　　　　　　　<!--全体の親要素--->
        <div class = "relative">

　　　　　　　<!--プルダウンメニュー親要素--->
        <div class = "pulldownM">

　　　　　　　<!--0～99までの年数用プルダウンメニュー--->
            19<select name="num" id = "pulldownYear">
                <option value=Infinity>-</option>
　　　　　　　　　　　　　　　<!--中略--->
                <option value="99">99</option>
                </select>年

　　　　　　　　<!--1～12までの月数用プルダウンメニュー--->    
                <select name="month" id = "pulldownMonth">
                    <option value=Infinity>-</option>
　　　　　　　　　　　　　　　<!--中略--->
                    <option value="12">12</option>
                    </select>月
    
　　　　　　　　　<!--1～31までの日数用プルダウンメニュー--->
                    <select name="day" id = "pulldownDay">
                        <option value=Infinity>-</option>
　　　　　　　　　　　　　　　<!--中略--->
                        <option value="31">31</option>
                        </select>日
        </div>
　　　　　　　<!--注意書き表示用要素--->
            <div id = "cautionId"> </div>

　　　　　　　<!--履歴年表作成ボタン要素--->
            <div class = buttonM>
                <div id = "button">履歴年表作成</div>
            </div>

　　　　　　　<!--履歴年表表示用要素--->
            <div class = "historyList">
                <div id = "output1">----</div>
                <div id = "output2">----</div>
                <div id = "output3">----</div>
                <div id = "output4">----</div>
                <div id = "output5">----</div>
                <div id = "output6">----</div>
                <div id = "output7">----</div>
                <div id = "output8">----</div>
            </div>

　　　　　　　<!--ラジオボタン用要素--->
            <div class = "ridiobuttonM">
                <input type="radio" name="q2" id = "radioA" checked> なし
                <input type="radio" name="q2" id = "radioB" > 専門・短大
                <input type="radio" name="q2" > 4年制大学
            </div>

        </div>
        <script src = main.js></script>
    </body>
</html>
```

```JavaScript
//履歴年表作成ボタンを押したらイベントスタート
const RirekiNenpyouSakusei = document.getElementById('button');
RirekiNenpyouSakusei.addEventListener('click', () => {

//プルダウンメニューの3つの値
const yearStatas = document.getElementById("pulldownYear");
const monthStatas = document.getElementById("pulldownMonth");
const dayStatas = document.getElementById("pulldownDay");

//プルダウンメニューの3つの値の前処理
const yearStatasN = Number(yearStatas.value) + 1900;
const monthN = Number(monthStatas.value) * 100;
const dayN = Number(dayStatas.value);

//100倍した月の値と、日の値を足す
let monthDayN = monthN + dayN;

//閏年の時（上）と閏年でない時（下）の除外する値
const NonDayLyear = [230, 231, 431, 631, 931, 1131];
const NonDayNotLyear = [229, 230, 231, 431, 631, 931, 1131];

//結果を表示させる際に使用する大域変数
let myHistory;

//入力制限用関数
function InputLimit(year, monthDay){
    if(!(year + monthDay === Infinity)){//プルダウンメニューでハイフンが選択されているかどうかの判定
        if( new Date(year, 1, 29).getDate() === 29 ){//閏年の判定
            if(NonDayLyear.includes(monthDay)){//閏年の時に除外する値の判定
                const cautionA = document.getElementById("cautionId");
                cautionA.textContent = `値が不正です。`;
                return Sakuzyo();
            }else{
                const erase = document.getElementById("cautionId");
                erase.textContent = ``;
                return history(monthDay);
            }
        }else{
            if(NonDayNotLyear.includes(monthDay)){//閏年でない時に除外する値の判定
                const cautionB = document.getElementById("cautionId");
                cautionB.textContent = `値が不正です。`;
                return Sakuzyo();            
            }else{
                const erase = document.getElementById("cautionId");
                erase.textContent = ``;
                return history(monthDay);
            }
        }
    }else{
        const cautionC = document.getElementById("cautionId");
        cautionC.textContent = `注意！未入力の箇所があります。`;
        return Sakuzyo();
    }
}


const history = function(set){//大域変数myHistoryに配列を設定するための関数
    let historyArray = [];
    if(set > 401){//4月1日以降であるかどうかを判定
        historyArray.push(yearStatasN + 7);
        historyArray.push(yearStatasN + 13);
        historyArray.push(yearStatasN + 16);
        historyArray.push(yearStatasN + 19);
        historyArray.push(yearStatasN + 21);
        historyArray.push(yearStatasN + 23);
        return myHistory = historyArray;
    }else{
        historyArray.push(yearStatasN + 6);
        historyArray.push(yearStatasN + 12);
        historyArray.push(yearStatasN + 15);
        historyArray.push(yearStatasN + 18);
        historyArray.push(yearStatasN + 20);
        historyArray.push(yearStatasN + 22);
        return myHistory = historyArray;
    }
}

InputLimit(yearStatasN, monthDayN);//入力制限用関数の呼び出し

try{//入力制限用関数で処理がはじかれた際にエラーを出してしまうのでそれを捕えるためのtry..catch...
    const pa1 = document.getElementById("output1");
    pa1.textContent = `小学校入学${myHistory[0]}年４月`;
    
    const pa2 = document.getElementById("output2");
    pa2.textContent = `小学校卒業${myHistory[1]}年３月`;
    
    const pa3 = document.getElementById("output3");
    pa3.textContent = `中学校入学${myHistory[1]}年４月`;
    
    const pa4 = document.getElementById("output4");
    pa4.textContent = `中学校卒業${myHistory[2]}年３月`;
    
    const pa5 = document.getElementById("output5");
    pa5.textContent = `高校入学${myHistory[2]}年４月`;
    
    const pa6 = document.getElementById("output6");
    pa6.textContent = `高校卒業${myHistory[3]}年３月`;
    
    const radiocheckA = document.getElementById('radioA');
    const radiocheckB = document.getElementById('radioB');
    
    if(radiocheckA.checked){//ラジオボタンの値によって表示を変化させる
        const pa7 = document.getElementById("output7");
        pa7.textContent = `----`;
    
        const pa8 = document.getElementById("output8");
        pa8.textContent = `----`;
        return;
    }else if(radiocheckB.checked){
        const pa7 = document.getElementById("output7");
        pa7.textContent = `専門・短大入学${myHistory[3]}年４月`;
    
        const pa8 = document.getElementById("output8");
        pa8.textContent = `専門・短大卒業${myHistory[4]}年３月`;
        return;
    }else{
        const pa7 = document.getElementById("output7");
        pa7.textContent = `大学入学${myHistory[3]}年４月`;
        
        const pa8 = document.getElementById("output8");
        pa8.textContent = `大学卒業${myHistory[5]}年３月`;
        return;
    }
}catch(err){
    console.log(`出力部分エラー:${err.message}`);//特に意味はない
}
});

const Sakuzyo = () => {//入力制限用関数ではじかれた際に出力済みの結果を消去する
    const pa1 = document.getElementById("output1");
    pa1.textContent = `----`;

    const pa2 = document.getElementById("output2");
    pa2.textContent = `----`;

    const pa3 = document.getElementById("output3");
    pa3.textContent = `----`;

    const pa4 = document.getElementById("output4");
    pa4.textContent = `----`;

    const pa5 = document.getElementById("output5");
    pa5.textContent = `----`;

    const pa6 = document.getElementById("output6");
    pa6.textContent = `----`;

    const pa7 = document.getElementById("output7");
    pa7.textContent = `----`;

    const pa8 = document.getElementById("output8");
    pa8.textContent = `----`;
}
```

##5. 更新版

コメントで日付を扱うときはDateオブジェクトを使用したほうが良い。というアドバイスを頂きました。
それを受けて、<b>「入力制限」と「4月2日の判定」の箇所をDateオブジェクトに置き換えました</b>ので、その更新内容を発表します。
なお、Dateオブジェクト置き換えに伴い、「閏年の判定」はする必要がなくなりました。

<h4>入力制限

まず新しく入力された値をDateオブジェクトに代入した変数setDateを用意しました。

```javascript
const yearStatasN = Number(yearStatas.value) + 1900;
const monthN = Number(monthStatas.value) - 1;
const dayN = Number(dayStatas.value);

let setDate = new Date(yearStatasN, monthN, dayN);
```
それを入力値の月(monthN)と比較してfalseならば、閏年の場合も含めた存在しない月日となります。

```javascript
InputLimit(hantei, setDate, monthN);

function InputLimit(HT, dateT, month){
    if(!(HT === Infinity)){
            if(dateT.getMonth() === month){//その月日が存在するかどうかを判定する
                const erase = document.getElementById("cautionId");
                erase.textContent = ``;
                return history(dateT);
            }else{
                const cautionA = document.getElementById("cautionId");
                cautionA.textContent = `値が不正です。`;
                return Sakuzyo();
            }
    }else{
        const cautionC = document.getElementById("cautionId");
        cautionC.textContent = `注意！未入力の箇所があります。`;
        return Sakuzyo();
    }
}

```
この方法は
[<b>JavaScriptで簡単に日付が有効かチェックする方法</b>](https://parashuto.com/rriver/development/validate-date-using-vanilla-js)
の記事を参考にしました。
これはDateオブジェクトに値を入力したとき、その月日が存在しない場合は翌月にズレるという特性を利用しています。

<h4>4月2日の判定

以下のように判定方法をDateオブジェクトに置き換えただけです。

```javascript
    if( new Date(yearStatasN, monthN, dayN) > new Date(yearStatasN, 3, 1)){}
```

<h4>ふりかえり

苦手だったDateオブジェクトから逃げていましたが、今回の課題を消化したことでDateオブジェクトに対し理解が深まりました。
アドバイスありがとうございました！
以下に更新したJavaScriptを載せます。

```javascript
//履歴年表作成ボタンを押したらイベントスタート
const RirekiNenpyouSakusei = document.getElementById('button');
RirekiNenpyouSakusei.addEventListener('click', () => {

//プルダウンメニューの3つの値
const yearStatas = document.getElementById("pulldownYear");
const monthStatas = document.getElementById("pulldownMonth");
const dayStatas = document.getElementById("pulldownDay");

//プルダウンメニューの3つの値の前処理
const yearStatasN = Number(yearStatas.value) + 1900;
const monthN = Number(monthStatas.value) - 1;
const dayN = Number(dayStatas.value);

//入力された値をDateオブジェクトに代入
let setDate = new Date(yearStatasN, monthN, dayN);

//プルダウンメニューでハイフンが選択されているかどうかの判定用定数
const hantei = yearStatasN + monthN + dayN;

let myHistory = [];

function InputLimit(HT, dateT, month){
    if(!(HT === Infinity)){//プルダウンメニューでハイフンが選択されているかどうかの判定
            if(dateT.getMonth() === month){//存在する月日かどうかを判定
                const erase = document.getElementById("cautionId");
                erase.textContent = ``;
                return history(dateT);
            }else{
                const cautionA = document.getElementById("cautionId");
                cautionA.textContent = `値が不正です。`;
                return Sakuzyo();
            }
    }else{
        const cautionC = document.getElementById("cautionId");
        cautionC.textContent = `注意！未入力の箇所があります。`;
        return Sakuzyo();
    }
}

//大域変数myHistoryに配列を設定するための関数
const history = function(set){  
   const myHistoryPush  = () =>{ 
        myHistory.push(set.setFullYear(set.getFullYear() + 6));
        myHistory.push(set.setFullYear(set.getFullYear() + 3));
        myHistory.push(set.setFullYear(set.getFullYear() + 3));
        myHistory.push(set.setFullYear(set.getFullYear() + 2));
        myHistory.push(set.setFullYear(set.getFullYear() + 2));
    }
    if( new Date(yearStatasN, monthN, dayN) > new Date(yearStatasN, 3, 1)){
        myHistory.push(set.setFullYear(set.getFullYear() + 7));
        myHistoryPush();
        return output(myHistory);
    }else{
        myHistory.push(set.setFullYear(set.getFullYear() + 6));
        myHistoryPush();
        return output(myHistory);
    }
}

InputLimit(hantei, setDate, monthN);//入力制限用関数の呼び出し

function output(MH){//出力用
    try{
        const pa1 = document.getElementById("output1");
        pa1.textContent = `小学校入学${new Date(MH[0]).getFullYear()}年４月`;
        
        const pa2 = document.getElementById("output2");
        pa2.textContent = `小学校卒業${new Date(MH[1]).getFullYear()}年３月`;
        
        const pa3 = document.getElementById("output3");
        pa3.textContent = `中学校入学${new Date(MH[1]).getFullYear()}年４月`;
        
        const pa4 = document.getElementById("output4");
        pa4.textContent = `中学校卒業${new Date(MH[2]).getFullYear()}年３月`;
        
        const pa5 = document.getElementById("output5");
        pa5.textContent = `高校入学${new Date(MH[2]).getFullYear()}年４月`;
        
        const pa6 = document.getElementById("output6");
        pa6.textContent = `高校卒業${new Date(MH[3]).getFullYear()}年３月`;
        
        const radiocheckA = document.getElementById('radioA');
        const radiocheckB = document.getElementById('radioB');
        
        if(radiocheckA.checked){//ラジオボタンの値によって表示を変化させる
            const pa7 = document.getElementById("output7");
            pa7.textContent = `----`;
        
            const pa8 = document.getElementById("output8");
            pa8.textContent = `----`;
            return;
        }else if(radiocheckB.checked){
            const pa7 = document.getElementById("output7");
            pa7.textContent = `専門・短大入学${new Date(MH[3]).getFullYear()}年４月`;
        
            const pa8 = document.getElementById("output8");
            pa8.textContent = `専門・短大卒業${new Date(MH[4]).getFullYear()}年３月`;
            return;
        }else{
            const pa7 = document.getElementById("output7");
            pa7.textContent = `大学入学${new Date(MH[3]).getFullYear()}年４月`;
            
            const pa8 = document.getElementById("output8");
            pa8.textContent = `大学卒業${new Date(MH[5]).getFullYear()}年３月`;
            return;
        }
    }catch(err){
        console.log(err);
    }
}

});

const Sakuzyo = () => {//入力制限用関数ではじかれた際に出力済みの結果を消去する
    const pa1 = document.getElementById("output1");
    pa1.textContent = `----`;

    const pa2 = document.getElementById("output2");
    pa2.textContent = `----`;

    const pa3 = document.getElementById("output3");
    pa3.textContent = `----`;

    const pa4 = document.getElementById("output4");
    pa4.textContent = `----`;

    const pa5 = document.getElementById("output5");
    pa5.textContent = `----`;

    const pa6 = document.getElementById("output6");
    pa6.textContent = `----`;

    const pa7 = document.getElementById("output7");
    pa7.textContent = `----`;

    const pa8 = document.getElementById("output8");
    pa8.textContent = `----`;
}
```

参考文献：初めてのJavaScript