第22話 顧客情報テーブルの作成
こんにちは!Kenです。
今回は前回サーバ側から引っ張ってきたデータを使って顧客情報テーブルのJavaScriptでコーディングしていきたいと思います。
fetchData( )メソッド内のコーディング
fetchDataメソッド内は次のようにコーディングします。
const fetchData = async () => {
try{
const response = await fetch(API_URL);
if(response.ok){
const data = await response.json();
createTable(data);
}else{
alert('HTTPレスポンスエラーです');
}
}catch(error){
alert('データ読み込み失敗です');
}
}
HTTPレスポンスがtrueの場合に次の処理に移るようにしてます。falseの場合は、alertを出すといった感じです。
そしてdataをcreateTableメソッドの引数として渡してあげてます。
createTable()メソッドの実装
画面上にテーブルを描写するcreateTableメソッドをコーディングしていきます。
コーディングの前にどんなテーブルとするかを決めておきたいと思います。各列項目を次のように設定しました。
■id: データのid。(id)
■名前:LINEの表示名。(display_name)
■登録日:友達になった日。(timestamp)
■カット時間:Cutに要する時間(cuttime)
■シャンプー時間:Shampooに要する時間(shampootime)
■カラーリング時間:Colorに要する時間(colortime)
■ヘッドスパ時間:Spaに要する時間(spatime)
■次回予約:次回の予約日時。
以上8つを列項目としたいと思います。
まずプログラムの一番上の無名関数のスコープ内で以下変数を宣言します。
const HEADERS = ['ID','名前','登録日','Cut','Shampoo','Color','Spa','次回予約'];
const CLASSES = ['row-id','row-name','row-resist','row-cut','row-shampoo','row-color','row-spa','row-nextrev'];
HEADERSはテーブルの表題に使う文字列、CLASSESは各列要素に適用するclass名を事前に配列として準備してます。こうすることで、後々使うことになる繰り返し構文(for やforEach)で、これら配列をインデックス値のみで扱いやすくなります。今はこのありがたみはわからなくともOKです。
ではcreateTable一気にいきます。我ながら技巧的なコードだと思います。
const createTable = (data) => {
// div要素の取得
const divElement = document.getElementById('usersPage');
// data.usersを2次元配列の形にする
const usersData = [];
data.users.forEach(usersObj=>{
// 現在時刻のタイムスタンプ取得
const now = new Date().getTime();
// data.reservationsからdata.usersのline_uidが一致するもの、かつ現在時刻より先の予約データのみを抽出
const revData = data.reservations.filter(revObj1=>{
return usersObj.line_uid === revObj1.line_uid;
}).filter(revObj2=>{
return parseInt(revObj2.starttime) > now;
});
// revData.starttimeを日時文字列へ変換する
const nextReservationDate = (revData.length) ? timeConversion(parseInt(revData[0].starttime),1) : '予約なし';
// usersObj.timestampを日時文字列へ変換する
const resistrationDate = timeConversion(parseInt(usersObj.timestamp),0);
// usersData配列へ配列を格納
usersData.push([
usersObj.id,
usersObj.display_name,
resistrationDate,
usersObj.cuttime,
usersObj.shampootime,
usersObj.colortime,
usersObj.spatime,
nextReservationDate
]);
});
// 次回予約日を計算し、usersDataへpushする
const l = usersData.length+1; //表題の分+1している
// テーブル要素の生成
const table = document.createElement('table');
table.setAttribute('id','usersTable');
for(let i=0;i<l;i++){
//tr要素の挿入
const tr = table.insertRow(-1);
HEADERS.forEach((value,index)=>{
if(i===0){
// 最初の行は表題(th)とする
const th = document.createElement('th');
th.setAttribute('class',`uTitles`);
th.innerHTML = value;
tr.appendChild(th);
}else{
// 2行目以降はユーザーデータを格納する要素とする
const td = document.createElement('td');
td.setAttribute('class',`uElements ${CLASSES[index]}`);
td.innerHTML = usersData[i-1][index];
tr.appendChild(td);
}
});
}
divElement.appendChild(table);
}
JavaScriptは楽しいですね。いかにシンプルに、美しく書くか。それに意識を集中させて書きました。もっとこうした方が美しい、シンプルであるというご意見ぜひメッセージください。
ポイントだけ解説します。
usersDataをわざわざ用意している理由です。dataは一次元配列なのですが、こんな形式になってます。[ {object1}, {object2}, ・・・]。これでもいいのですが、後々、繰り返し構文で使うには次のような形式になっていた方が扱いやすいのです。[ [array1], [array2], ・・・]。配列の中に配列が入った形。2次元配列です。なぜかというと配列は要素の取り出しにインデックス値が使えるからです。オブジェクトだと、object.nameみたいにインデックス値が扱えません。2次元配列形式へ変換すれば、array[i][j]みたいな形で容易に要素を取り出すことができます。
data.usersは[ {object1}, {object2}, ・・・]のような形式で、これに対しforEachをかけてます。
このforEachの中で、data.reservationsをいじっている箇所があります。これは、「次回予約」のデータを得るための操作です。data.reservationsに2つのフィルターを連続してかけているのがわかりますか?
1つ目のフィルターが data.reservationsからdata.users.line_uidと一致するデータのみを抽出している部分です。
2つ目のフィルターがstarttimeがnowより大きなデータを抽出している部分です。
この2つのフィルターをかけることで、revDataには”対象ユーザー”の"未来の予約"があるデータのみが格納されます。
revDataにデータが格納されていれば、revData.starttimeをtimeConversionの引数に渡してあげ、対象データがなければ、'予約なし'の文字列を返してあげてます。
timeConversion関数はタイムスタンプを日時文字列に変換する自作関数です。後ほど解説します。
そして2次元配列のusersDataへ配列をpushしています。HEADERSで宣言した表題に合わせて('ID','名前','登録日','Cut','Shampoo','Color','Spa','次回予約')、その順番で配列を作り、それをpushしています。
あとはtable要素をDOM操作でどんどん作っていくだけです。テーブルは繰り返し文で作ることが楽なため、for や forEachを駆使して作ります。そのためにわざわざ2次元配列への変換を行ったわけです。
th.setAttribute('class',`uTitles ${CLASSES[index]}`);
と
td.setAttribute('class',`uElements ${CLASSES[index]}`);
は事前に用意したCLASSESをindexによってclass名へ適用してあげてます。とことんコードをシンプルにする工夫です。
timeConversion関数の実装
タイムスタンプを日時文字列へ変換する関数を実装したいと思います。今のところ変換したいタイムスタンプは以下の2つです。
■友達登録日 → YYYY/MM/DD形式で良い
■次回予約日時 → YYYY/MM/DD HH:MMで欲しい
usersテーブルある友達登録日とreservationsテーブルにある次回予約日時のタイムスタンプを変換したいのですが、両者で欲しい形式が異なります。友達登録は時間まではいらないですし、次回予約日時は時間まで欲しいです。
同じ関数を使いますが、次のように返してあげる文字列を選別することができます。
// revData.starttimeを日時文字列へ変換する
const nextReservationDate = (revData.length) ? timeConversion(parseInt(revData[0].starttime),1) : '予約なし';
// usersObj.timestampを日時文字列へ変換する
const resistrationDate = timeConversion(parseInt(usersObj.timestamp),0);
timeConversion関数の第二引数にそれぞれ1と0が入ってます。この1,0で返してあげる文字列を区別してあげるのです。
ではtimeConversion関数です。
const timeConversion = (timestamp,mode) => {
const date = new Date(timestamp);
const y = date.getFullYear();
const m = ("0" + (date.getMonth()+1)).slice(-2);
const d = ("0" + date.getDate()).slice(-2);
const h = ("0" + date.getHours()).slice(-2);
const i = ("0" + date.getMinutes()).slice(-2);
if(mode === 0){
return `${y}/${m}/${d}`
}else{
return `${y}/${m}/${d} ${h}:${i}`
}
}
第二引数にmodeを設定し、このmodeにより返る文字列を区別しました。
CSSファイルの編集
ここまでできましたらherokuへデプロイしてみましょう。そして /usersページを読み込みます。
全く味気ないけど、テーブルらしきものができましたね。
ではこの味気ないテーブルにCSSで味付けしていきましょう。
顧客管理画面のmain.cssは以下です。
/* 顧客管理画面 */
#usersPage{
text-align:center;
}
#usersTable{
margin-top:2vw;
margin-left:2vw;
}
.uTitles{
border:solid 1px #aaa;
text-align:center;
background:#333;
color:#fff;
font-size:2vw;
margin-top:1vw;
}
.uElements{
border:solid 1px #aaa;
text-align:center;
font-size:2vw;
height:3vh;
}
.row-id{
width:6vw;
font-size:2vw;
margin-left:1vw;
}
.row-name{
width:23w;
font-size:2.5vw;
}
.row-resist{
width:18vw;
font-size:2.5vw;
}
.row-cut{
width:8vw;
font-size:2.5vw;
}
.row-shampoo{
width:10vw;
font-size:2.5vw;
}
.row-color{
width:8vw;
font-size:2.5vw;
}
.row-spa{
width:8vw;
font-size:2.5vw;
}
.row-nextrev{
width:18vw;
font-size:2.5vw;
}
それでは、ページを読み込んでいきましょう。
それっぽい画面になりましたね。
今回はここまでにします。
次回は、各個人の施術時間(カット、シャンプー、カラーリング、ヘッドスパ)を変更できるようにしたいと思います。
これはフロント側からバック側のデータを更新しにいくので、またAPIサーバの登場ですね。
本記事が少しでも参考になりましたら「スキ」をいただけると幸いです。
ご不明点やメンターご依頼はMENTAにてお気軽にお問合せください。
最後までお読みいただき、ありがとうございました。