【JavaScript】二次元配列で所属ごとの勤務カレンダーを表示

勤めている職場で、職員の出勤予定を見られるシステムが欲しいという要望がありました。

タイムカードのシステムを入れ替えたところで、入れ替え以前は誰でも予定を確認できたのが、現行のシステムでは管理者が自分の管理範囲内でしか見られないようになってしまったのです。

本来はその方がいいのかもしれませんが、出勤予定を確認してから電話連絡するというやり方を取っていた職員にとっては不便になってしまいました。

Excelだとデータを壊される可能性が高いですし、PDFだとフィルタがかけられないしどの日付の列かがわかりにくいです。そこでHTML上に表示してJavaScriptで所属ごとに表示するカレンダーを作りました。

スポンサーリンク

プログラムの内容

こちらがサンプルファイルです。JavaScript入りのページと、勤務表からJS用のコードを出力するVBAマクロ入りExcelファイルが入っています。。

こんな感じに表示します。最初は何も表示されていませんが、表示ボタンをクリックすることでスクリプトが実行されます。

リストから所属を選択して実行するとその所属のみに絞り込まれ、「全員」で実行すると全員分表示されます。

この元データはJavaScriptの二次元配列を直接HTMLに入力しています。その方法は使っているシステムによっていろいろですが、今回はExcelの勤務表から出力します。

勤務表のサンプルを作るのに自動作成ツールが役立ちました。所属ごとの勤務表を作成します。

実行ボタンを用意していませんが、「JSコード出力」というマクロを実行します。

マクロのExcelブックと同じディレクトリに「export.txt」が作成されます。その中身は勤務表のデータに従って作成されたJavaScriptの二次元配列です。これをHTMLに入力してやります。

最初の行は1つでOKなので、2つ目以降の所属については2行目からコピペしてください。

プログラミング

HTML

<!DOCTYPE html>
<html lang="ja">
<head>
<title>202105勤務カレンダー</title>
<script>
var userAllData = new Array();
var colTitle = new Array();
function showTable() {
    var userAllData = [
    // ここに出力したデータを入力
['所属','氏名','01(土)','02(日)','03(月)','04(火)','05(水)','06(木)','07(金)','08(土)','09(日)','10(月)','11(火)','12(水)','13(木)','14(金)','15(土)','16(日)','17(月)','18(火)','19(水)','20(木)','21(金)','22(土)','23(日)','24(月)','25(火)','26(水)','27(木)','28(金)','29(土)','30(日)','31(月)'],
['湘北','赤木','遅1','公休','-','公休','遅2','-','-','早2','早1','公休','公休','遅2','-','-','早2','公休','-','早1','公休','公休','-','早1','-','早1','公休','-','遅1','-','公休','公休','遅2'],
['湘北','小暮','-','早2','公休','早1','遅1','公休','遅1','-','-','早2','-','公休','遅2','-','公休','公休','早1','公休','-','早1','-','早2','公休','遅2','遅2','公休','早2','公休','-','遅2','公休'],
['湘北','安田','遅2','-','-','早2','夜','夜','休','休','遅2','-','早2','-','-','早1','-','-','早2','-','早1','遅1','遅1','-','早1','-','夜','夜','休','休','遅1','-','早2'],
['湘北','宮城','公休','-','早1','遅1','-','公休','早2','早1','-','遅2','夜','夜','公休','公休','公休','早2','遅2','-','遅1','公休','早2','公休','夜','夜','公休','公休','公休','早1','遅2','-','遅1'],
['湘北','潮崎','早1','遅1','遅1','-','-','-','早1','遅2','遅1','-','早1','-','遅1','-','夜','夜','休','休','-','遅2','夜','夜','休','休','遅1','-','早1','-','早1','-','-'],
['湘北','角田','夜','夜','休','休','-','早2','-','遅1','-','-','-','早2','-','早2','早1','遅2','遅1','-','夜','夜','休','休','遅2','遅1','-','早2','-','-','早2','遅1','-'],
['湘北','桜木','公休','-','早2','-','公休','早1','-','公休','公休','-','遅2','-','夜','夜','公休','公休','夜','夜','公休','公休','早1','遅1','-','-','早1','遅2','-','早2','公休','公休','早1'],
['湘北','流川','-','公休','夜','夜','公休','公休','公休','-','公休','-','-','遅1','-','遅1','遅1','-','公休','公休','遅2','-','-','-','公休','-','-','公休','-','遅2','夜','夜','公休'],
['湘北','石井','早2','-','-','-','早1','-','-','-','-','早1','遅1','-','早1','遅2','-','早1','-','遅1','-','早2','遅2','-','早2','-','-','早1','-','-','-','早1','-'],
['湘北','佐々岡','-','早1','遅2','-','早2','遅2','遅2','-','早2','遅1','-','-','早2','-','-','遅1','-','早2','早2','-','-','遅2','-','早2','早2','-','遅2','遅1','-','早2','-'],
['湘北','三井','-','遅2','公休','遅2','-','遅1','夜','夜','公休','公休','-','早1','公休','-','遅2','-','公休','遅2','公休','-','公休','-','遅1','公休','-','遅1','夜','夜','公休','公休','-'],
['湘北','桑田','-','-','-','-','-','-','-','-','夜','夜','休','休','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','-','夜'],
['陵南','魚住','公休','-','早','遅1','遅1','遅1','公休','遅2','公休','-','早','早','公休','公休','遅1','-','公休','早','遅1','遅1','遅2','公休','遅1','-','公休','遅2','公休','遅1','公休','遅1','遅1'],
['陵南','池上','遅2','公休','遅1','-','公休','早','早','遅1','遅1','公休','公休','遅1','-','早','遅2','公休','早','遅1','公休','早','公休','遅2','公休','遅2','-','早','遅1','公休','遅1','公休','-'],
['陵南','越野','-','早','夜','夜','公休','公休','遅2','-','-','早','遅2','遅2','夜','夜','公休','公休','遅1','-','遅2','公休','夜','夜','公休','公休','夜','夜','公休','公休','早','遅2','公休'],
['陵南','仙道','夜','夜','公休','公休','早','公休','遅1','-','夜','夜','公休','公休','遅1','遅1','公休','早','遅2','遅2','-','-','早','遅1','夜','夜','公休','公休','夜','夜','公休','公休','早'],
['陵南','植草','遅1','遅1','-','早','遅2','-','夜','夜','休','休','夜','夜','休','休','-','遅2','夜','夜','休','休','-','早','遅2','遅1','遅2','-','早','-','夜','夜','休'],
['陵南','福田','-','遅2','公休','-','夜','夜','公休','公休','早','遅2','-','公休','遅2','遅2','夜','夜','公休','公休','夜','夜','公休','公休','早','公休','早','公休','-','早','遅2','-','夜'],
['陵南','相田','早','-','遅2','遅2','-','遅2','-','早','遅2','遅1','遅1','-','早','-','早','遅1','-','-','早','遅2','遅1','-','-','早','遅1','遅1','遅2','遅2','-','早','遅2'],
['海南','牧','-','遅','公休','遅','-','早','公休','-','公休','早','-','遅','-','公休','早','遅','遅','-','遅','公休','公休','-','遅','公休','早','公休','公休','遅','公休','早','遅'],
['海南','高砂','公休','-','早','-','公休','遅','-','早','-','公休','公休','早','公休','早','-','公休','-','早','公休','遅','遅','公休','早','早','遅','-','遅','公休','公休','-','早'],
['海南','神','早','-','-','早','夜','夜','公休','公休','早','遅','夜','夜','公休','公休','遅','公休','夜','夜','公休','公休','早','遅','-','遅','公休','遅','夜','夜','公休','公休','-'],
['海南','武藤','遅','公休','夜','夜','公休','公休','遅','遅','夜','夜','公休','公休','早','-','公休','早','-','公休','早','-','-','早','夜','夜','公休','公休','早','-','早','公休','夜'],
['海南','清田','-','早','遅','公休','早','-','夜','夜','公休','公休','遅','-','夜','夜','公休','公休','早','遅','夜','夜','公休','公休','-','公休','夜','夜','公休','公休','遅','遅','-'],
['海南','宮益','夜','夜','休','休','遅','-','早','-','遅','-','早','-','遅','遅','夜','夜','休','休','-','早','夜','夜','休','休','-','早','-','早','夜','夜','休'],
['翔陽','藤間','-','早','公休','-','公休','早','公休','公休','-','早','遅','-','遅','-','早','公休','公休','早','遅','-','-','遅','公休','早','遅','遅','公休','早','遅','公休','公休'],
['翔陽','花形','公休','遅','-','早','遅','公休','遅','早','遅','-','公休','早','公休','遅','公休','-','早','公休','公休','-','-','早','早','遅','公休','-','早','公休','公休','遅','遅'],
['翔陽','長谷川','遅','-','早','遅','公休','-','夜','夜','公休','遅','夜','夜','公休','公休','公休','早','公休','-','夜','夜','公休','-','遅','公休','早','早','遅','公休','夜','夜','公休'],
['翔陽','永野','夜','夜','公休','公休','早','遅','-','遅','夜','夜','公休','-','早','公休','遅','公休','遅','-','早','公休','夜','夜','公休','公休','夜','夜','公休','遅','早','早','公休'],
['翔陽','高野','早','公休','遅','-','夜','夜','公休','公休','早','公休','-','-','夜','夜','公休','遅','夜','夜','公休','遅','早','-','夜','夜','公休','公休','公休','-','公休','-','早'],
['翔陽','伊藤','-','公休','夜','夜','公休','公休','早','-','公休','-','早','遅','公休','早','夜','夜','公休','遅','公休','早','遅','-','-','-','公休','-','夜','夜','公休','公休','夜']
    // データここまで
    ];
    // 先頭の行を列のタイトルにセット
    colTitle = userAllData[0];
    // 先頭の行をデータから削除
    userAllData.shift();
    // 所属の選択
    var userData = new Array();
    var shozoku = document.getElementById("shozoku").value;
    var n = 0;
    for(var i=0;i<userAllData.length;i++){
        if(shozoku=='全員'){
            userData[n] = userAllData[i];
            n++;
        } else {
            if(userAllData[i][0]==shozoku){
                userData[n] = userAllData[i];
                n++;
            }
        }
    }
    // 表の作成
    var table = document.getElementById("dataTable");
    table.innerHTML = "";
    var thead = document.createElement("thead");
    var tbody = document.createElement("tbody");
    for(i=-1;i<userData.length;i++){
        // trの作成
        var tr = document.createElement("tr");
        if(i!=-1){
            if (i%2==0) {
                tr.classList.add('evenRow')
            } else {
                tr.classList.add('oddRow')
            }
        }
        for(j=1;j<userData[0].length;j++){
            if (i<0){
                // 列タイトル
                var th = document.createElement("th");
                th.innerHTML = colTitle[j];
                // trに追加
                tr.appendChild(th);
                thead.appendChild(tr);
            } else {
                if(j==1){
                    var th = document.createElement("th");
                    th.innerText = userData[i][j];
                    // trに追加
                    tr.appendChild(th);
                } else {
                    // データ
                    var td = document.createElement("td");
                    td.innerText = userData[i][j];
                    // trに追加
                    tr.appendChild(td);
                }
            }
        }
        // tableに追加
        if(i<0) {
            table.appendChild(thead);
        } else {
            tbody.appendChild(tr);
        }
    }
    table.appendChild(tbody);
}
</script>
<style>
    table {
        border-collapse: separate;
        border-spacing: 0px;
        width: 100%;
    }
    th, td {
        border: 1px solid gray;
        white-space: nowrap;
        text-align: center;
    }
    /* ヘッダー行の背景色 */
    th {
        background-color: #9dd3a8;
    }
    /* 奇数行の背景色 */
    .oddRow th, .oddRow td {
        background-color: #ceefe4;
    }
    /* 偶数行の背景色 */
    .evenRow th, .evenRow td {
        background-color: #f4f0e6;
    }
    /* ヘッダー行の固定 */
    #dataTable thead th {
        position: -webkit-sticky;
        position: sticky;
        top: 0;
        z-index: 1;
    }
    /* ヘッダー列の固定 */
    #dataTable th:first-child {
        position: -webkit-sticky;
        position: sticky;
        left: 0;
    }
    /* 左上のセルの固定 */
    #dataTable thead th:first-child {
        z-index: 2;
    }
    /* 表示エリア */
    #showArea {
        overflow: scroll;
        width: 100%;
        height: 75vh;
    }
</style>
</head>
<body>
<p>202105勤務カレンダー</p>
<select id="shozoku" name=”shozoku”>
    <option value='全員'>全員</option>
    <option value='湘北'>湘北</option>
    <option value='陵南'>陵南</option>
    <option value='海南'>海南</option>
    <option value='翔陽'>翔陽</option>
</select>
<input type="button" value="表示" onclick="showTable();"/>
<hr />
<div id = "showArea">
    <table id="dataTable"></table>
</div>
</body>
</html>

「// ここに出力したデータを入力」のコメントの下に二次元配列を入力します。これがそのままuserAllDataの中身になります。

実行時に選択されている所属のみuserDataに格納して、その配列を元にテーブルを作成します。所属のリストはHTMLで。

奇数行と偶数行で色を分けて見やすくするため、oddRowとevenRowというクラス名を付与し、CSSで色を変えています。

ヘッダー行・列を固定する

地味に苦労したのがスクリプトよりも、スクロール時にヘッダー行と列を固定するCSSの方でした。

CSSのposition: stickyでテーブルのヘッダー行・列を固定する - Qiita
CSSの position: sticky を使ってテーブルのヘッダー行・列を固定する方法を解説します。動作確認したブラウザーは次のとおりです。Google Chrome 71Firefox 64…

こちらの記事を参考にしました。stickyっていうプロパティを使います。迷ったのが行と列を固定しても左上のセルだけ動いてしまうところで、左上のセルだけは単独で指定する必要があるんですね。

あと、スクロールした時に罫線が消えてしまったり、そもそもFireFoxでは線が消えてしまうといった場合は、border-collapseプロパティをcollapseで指定していると起きるようで、separeteにしてborder-spacingを0pxにすると綺麗に見えます。

VBA

Sub JSコード出力()
    Dim shozoku As String       '所属
    Dim rowEnd As Integer       '最終行
    Dim colEnd As Integer       '最終列
    Dim tableArr() As Variant   '二次元配列
    Dim txtFileName As String   '出力ファイル名
    Dim setStr As String        '1行分の文字列
    '二次元配列を作成
    shozoku = Cells(1, 1).Value
    Cells(2, 1).Select
    rowEnd = Selection.End(xlDown).Row
    colEnd = Selection.End(xlToRight).Column
    tableArr = Range(Cells(2, 1), Cells(rowEnd, colEnd))
    'テキストファイルに出力
    txtFileName = ThisWorkbook.Path & "\export.txt"
    Open txtFileName For Output As #1
    'ヘッダー行
    setStr = "['所属','氏名'"
    For i = 3 To UBound(tableArr, 2)
        '1桁の日付は頭に「0」を付ける
        If Len(tableArr(1, i)) = 1 Then
            tableArr(1, i) = "0" & tableArr(1, i)
        End If
        setStr = setStr & ",'" & tableArr(1, i) & "(" & tableArr(2, i) & ")'"
    Next
    setStr = setStr & "],"
    Print #1, setStr
    '各職員行を処理
    For i = 3 To UBound(tableArr, 1)
        setStr = "['" & shozoku & "','" & tableArr(i, 1) & "'"
        For j = 3 To UBound(tableArr, 2)
            '空白セルは「-」にする
            If tableArr(i, j) = "" Then
                tableArr(i, j) = "-"
            End If
            setStr = setStr & ",'" & tableArr(i, j) & "'"
        Next j
        setStr = setStr & "]"
        '最終行以外は「,」を付ける
        If i <> UBound(tableArr, 1) Then
            setStr = setStr & ","
        End If
        Print #1, setStr
    Next i
    Close #1
End Sub

二次元配列の作り方は環境によって異なりますが、今回はExcelの勤務表を1行ずつテキストデータに出力していく方法を取りました。

コメント

コメントする前にお読みください

迷惑コメント防止のために初回のコメント投稿は承認制のため、投稿が反映されるまで少し時間がかかります。もちろん荒らしは承認しません。

教えて君やクレクレ君に対しては回答しませんのでご了承ください。