第23話 顧客情報編集用カード作成

こんにちは!Kenです。

前回は、サーバ側から引っ張ってきたデータをフロント側でテーブル可視化しました。

今回の美容院自動予約システムの特徴は、同じメニューであっても各個人ごとに施術時間を自由に設定できるということです。女性と男性ではカット時間が当然異なりますもんね。その情報を編集するためのカードを作成していきたいと思います。

編集カード・・・?

では、編集画面をどうするか。別ページへ遷移しても良いのですが、このためだけにわざわざ・・・って感じなので、編集カードなるものを作ろうと思います。

同じお客様情報画面上で立ち上がる各情報が記載されたカードをイメージいただければと思います。

構想は、カット・シャンプー・カラー・スパの施術時間をクリック(タップ)したら同画面上で編集カードが立ち上がるものを想定します。

まず、各施術時間をクリック(タップ)したときの処理をコーディングします。

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];
       
       // 施術時間をクリックした時の処理
       if(index >= 3 && index <= 6){
           td.addEventListener('click',(e)=>{
               const x = e.pageX;
               const y = e.pageY;
               createCard(usersData[i-1],x,y);
           });
       }
       tr.appendChild(td);
   }
});

追加された箇所は、indexが3以上6以下の時、すなわち、カット〜ヘッドスパのテーブル要素の時、その要素をクリックしたらcreateCardメソッドを実行する部分のところです。

さらに引数として、クリックしたお客様のusersDataとクリックした位置座標を渡してあげてます。

createCard()メソッドの実装

実際にお客様情報が記されたカードを生成する関数createCard()を実装します。なぜcardかというと、Bootstrapのコンポーネントの中のcardを使うからです。

では、カードに載せる情報を決めましょう。

■ID:データテーブル上のID
■名前:データテーブルに登録された名前(display_name)
■カット時間:変更できるようにinput要素で。
■シャンプー時間:変更できるようにinput要素で。
■カラーリング時間:変更できるようにinput要素で。
■ヘッドスパ時間:変更できるようにinput要素で。
■編集ボタン:各時間を編集できるようにする。
■削除ボタン:ユーザー情報を削除できるようにする。
■送信ボタン:編集したデータをサーバ側へ送信する。
■閉じるボタン:カード自体を閉じる

こんなところでしょうか。

今回もJavaScriptでゴリゴリ書いていきますよ。

const createCard = (userDataArray,x,y) => {

   // カード本体の定義
   const divCard = document.createElement('div');
   divCard.setAttribute('class','card text-white bg-primary card-user');
   divCard.style.top = `${y}px`;
   divCard.style.left = `${x/2}px`;
   
   // カードヘッダーの定義
   const divHeader = document.createElement('div');
   divHeader.setAttribute('class','card-header');
   divHeader.innerHTML = `お客さまID:${userDataArray[0]}`;
   divCard.appendChild(divHeader);
   
   // カードボディの定義
   const divBody = document.createElement('div');
   divBody.setAttribute('class','card-body');
   
   // form要素の生成
   const formElement = document.createElement('form');
   formElement.setAttribute('id','userForm');
   formElement.setAttribute('name','userInfo');
   formElement.setAttribute('method','post');
   
   // 名前入力フォームの生成
   const div_form_name = document.createElement('div');
   div_form_name.setAttribute('class','form-group');
   const label_name = document.createElement('label');
   label_name.setAttribute('class','label_user');
   label_name.innerHTML = '名前';
   div_form_name.appendChild(label_name);
   const input_name = document.createElement('input');
   input_name.setAttribute('type','text');
   input_name.setAttribute('class','form-control name-input');
   input_name.setAttribute('name','name');
   input_name.value = userDataArray[1];
   input_name.disabled = true;
   div_form_name.appendChild(input_name);
   formElement.appendChild(div_form_name);
   
   // カット時間入力フォームの生成
   const div_form_cut = document.createElement('div');
   div_form_cut.setAttribute('class','form-group inline-block menu-time');
   const label_cut = document.createElement('label');
   label_cut.setAttribute('class','label_user');
   label_cut.innerHTML = 'Cut';
   div_form_cut.appendChild(label_cut);
   const input_cut = document.createElement('input');
   input_cut.setAttribute('type','text');
   input_cut.setAttribute('class','form-control time-input');
   input_cut.setAttribute('name','cuttime');
   input_cut.value = userDataArray[3];
   input_cut.disabled = true;
   div_form_cut.appendChild(input_cut);
   formElement.appendChild(div_form_cut);
   
   // シャンプー時間の入力フォーム生成
   const div_form_shampoo = document.createElement('div');
   div_form_shampoo.setAttribute('class','form-group inline-block');
   const label_shampoo = document.createElement('label');
   label_shampoo.setAttribute('class','label_user');
   label_shampoo.innerHTML = 'Shampoo';
   div_form_shampoo.appendChild(label_shampoo);
   const input_shampoo = document.createElement('input');
   input_shampoo.setAttribute('type','text');
   input_shampoo.setAttribute('class','form-control time-input');
   input_shampoo.setAttribute('name','shampootime');
   input_shampoo.value = userDataArray[4];
   input_shampoo.disabled = true;
   div_form_shampoo.appendChild(input_shampoo);
   formElement.appendChild(div_form_shampoo);
   
   // カラーリング時間の入力フォーム生成
   const div_form_color = document.createElement('div');
   div_form_color.setAttribute('class','form-group inline-block menu-time');
   const label_color = document.createElement('label');
   label_color.setAttribute('class','label_user');
   label_color.innerHTML = 'Color';
   div_form_color.appendChild(label_color);
   const input_color = document.createElement('input');
   input_color.setAttribute('type','text');
   input_color.setAttribute('class','form-control time-input');
   input_color.setAttribute('name','colortime');
   input_color.value = userDataArray[5];
   input_color.disabled = true;
   div_form_color.appendChild(input_color);
   formElement.appendChild(div_form_color);
   
   // ヘッドスパ時間の入力フォーム生成
   const div_form_spa = document.createElement('div');
   div_form_spa.setAttribute('class','form-group inline-block');
   const label_spa = document.createElement('label');
   label_spa.setAttribute('class','label_user');
   label_spa.innerHTML = 'Spa';
   div_form_spa.appendChild(label_spa);
   const input_spa = document.createElement('input');
   input_spa.setAttribute('type','text');
   input_spa.setAttribute('class','form-control time-input');
   input_spa.setAttribute('name','spatime');
   input_spa.value = userDataArray[6];
   input_spa.disabled = true;
   div_form_spa.appendChild(input_spa);
   formElement.appendChild(div_form_spa);
   
   // 子要素の親要素へのappendChild
   divBody.appendChild(formElement);
   divCard.appendChild(divBody);
   
   // ボタン要素の作成
   const divButton = document.createElement('div');
   divButton.setAttribute('id','usercard-button-area');
   
   //編集ボタンの作成
   const editButton = document.createElement('input');
   editButton.setAttribute('class','btn btn-warning card-button');
   editButton.value = '編集';
   editButton.type = 'button';
   
   //編集ボタンクリック時の動作
   editButton.addEventListener('click',()=>{
   
       //クリック時の処理を後で実装
       
   });
   divButton.appendChild(editButton);
   
   //削除ボタンの作成
   const deleteButton = document.createElement('input');
   deleteButton.setAttribute('class','btn btn-danger card-button');
   deleteButton.value = '削除';
   deleteButton.type = 'button';
   deleteButton.addEventListener('click',()=>{
   
       // クリック時の処理を後で実装
       
   });
   divButton.appendChild(deleteButton);
   divCard.appendChild(divButton);
   
   //フッターの作成(フッター領域をクリックするとカードが消える)
   const divFooter = document.createElement('div');
   divFooter.setAttribute('class','card-footer text-center');
   divFooter.setAttribute('id','close-form');
   const closeButton = document.createElement('a');
   closeButton.setAttribute('class','closeButton');
   closeButton.textContent = '閉じる';
   divFooter.addEventListener('click',()=>{
       divCard.style.display = 'none';
   });
   divFooter.appendChild(closeButton);
   divCard.appendChild(divFooter);
   
   document.body.appendChild(divCard);
}

結構長いコードですが、ほとんどは同じことの繰り返しなので、実質はあまり中身はありません。

一番下のフッター領域の「閉じる」をクリックすることで、このカード自体を閉じることとします。

カット時間(ラベル、インプット)〜ヘッドスパ時間(ラベル、インプット)はほぼ同じことの繰り返しだから、for文とか使えばもっとシンプルに書けるのかな?今回はストレートに書いてみました。

そしてCSSです。

/* 顧客情報カード */
.card-user{
   position:absolute;
   width:40vw;
   height:auto;
   text-align:left;
   border:1px solid #aaa;
   box-shadow:2px 2px 4px #888;
   font-size:2vw;
}

.card-header{
   padding:1vw;
   background-color:#09f;
   font-size:2vw;
}

.card-body{
   padding:0.5vw 2vw;
}

.form-group{
   margin:0vw 1vw;
}
.label_user{
   margin-bottom:0;
}

.name-input{
   width:32vw;
   height:4vw;
   font-size:2.5vw;
}

.time-input{
   width:14vw;
   height:4vw;
   font-size:2.5vw;
}

.menu-time{
   margin-right:3vw;
}

.form-control{
   padding:0.5vw 0.5vw;
}

#usercard-button-area{
   margin:1vw 1vw;
}

.card-button{
   font-size:2vw;
   padding:1vw 1vw;
   width:14vw;
   margin:2vw 2vw;
}

.card-footer{
   padding:1vw 0;
   background-color:#aaa;
   font-size:2vw;
 }
 
.closeButton{
   width:30vw;
}

#close-form:hover{
   background-color:#333;
   cursor:pointer;
}
 
 #close-form:active{
   background-color:#333;
   cursor:pointer;
}

このCSSを書くのに結構骨が折れました・・・PCとスマホ両方でちょうどよく見えるようにするため、結構marginやらpaddingやらfont-sizeやらの微調整が結構時間かかってしまいました。

ChromeのJavaScriptコンソールのElementsを確認しながらやると、捗りますよ。

フッターへマウスカーソル合わせると色とポインタが変わるなんて小細工もしております。

せっかくなので、カット、シャンプー、カラーリング、ヘッドスパの施術時間のテーブル要素にカーソルを合わせた際も色とポインタが変わるようにしたいですね。

CSSに以下を追加しました。

.row-cut:hover{
   background-color:rgb(222, 231, 97);
   cursor:pointer;
}

.row-shampoo:hover{
   background-color:rgb(222, 231, 97);
   cursor:pointer;
}

.row-color:hover{
   background-color:rgb(222, 231, 97);
   cursor:pointer;
}

.row-spa:hover{
   background-color:rgb(222, 231, 97);
   cursor:pointer;
}

ではこれでデプロイしてみましょう。

スクリーンショット 2020-11-01 20.13.57

画面にマウスのポインタは写っておりませんが、ID1の人のシャンプーにカーソルを合わせると上のように黄色く色が変わりました。

そのままそこをクリックすると、

スクリーンショット 2020-11-01 20.14.21

このようなカードが立ち上がります。IDは1となっていることがわかりますね。名前ももちろん1の人の名前です。

そして閉じるをクリックするとカードが消えます。

ここまでできればOKです。

カードをドラッグ&ドロップで移動させる

カードの出現位置はx,yで指定したものの、カードを移動させることができると便利ですよね。ドラッグ&ドロップによる移動を実装します。

ヘッダー部分(お客さまID:Xの領域)をクリック(タップ)したらドラッグ&ドロップできるようにしてみましょう。

以下のコードをcreateCard()メソッドの中に追加します。

// マウスイベント
divHeader.onmousedown = (e) =>{
   let shiftX = e.clientX - divCard.getBoundingClientRect().left;
   let shiftY = e.clientY - divCard.getBoundingClientRect().top;
   
   const moveAt = (pageX,pageY) => {
       if(pageX-shiftX>=0){
           divCard.style.left = pageX - shiftX + 'px';
       }else{
           divCard.style.left = 0 + 'px';
       }
       if(pageY-shiftY>=0){
           divCard.style.top = pageY - shiftY + 'px';
       }else{
           divCard.style.top = 0;
       }
   }
   
   moveAt(e.pageX,e.pageY);
   
   const onMouseMove = (e) => {
       moveAt(e.pageX,e.pageY);
   }
   
   document.addEventListener('mousemove',onMouseMove);
   
   divHeader.onmouseup = () => {
       document.removeEventListener('mousemove',onMouseMove);
       divHeader.onmouseup = null;
   }
   
   divHeader.onmouseleave = () => {
       document.removeEventListener('mousemove',onMouseMove);
       divHeader.onmouseleave = null;
   }
}

// タッチイベント
divHeader.ontouchstart = (event) =>{
   const e = event.changedTouches[0];
   let shiftX = e.clientX - divCard.getBoundingClientRect().left;
   let shiftY = e.clientY - divCard.getBoundingClientRect().top;
   
   const moveAt = (pageX,pageY) => {
       if(pageX-shiftX>=0){
           divCard.style.left = pageX - shiftX + 'px';
       }else{
           divCard.style.left = 0 + 'px';
       }
       if(pageY-shiftY>=0){
           divCard.style.top = pageY - shiftY + 'px';
       }else{
           divCard.style.top = 0;
       }
   }
   
   moveAt(e.pageX,e.pageY);
   
   const onMouseMove = (event) => {
       const e = event.changedTouches[0];
       moveAt(e.pageX,e.pageY);
   }
   
   document.addEventListener('touchmove',onMouseMove);
   
   divHeader.ontouchend = () => {
       document.removeEventListener('touchmove',onMouseMove);
       divHeader.ontouchend = null;
   }
}

divHeader.ondragstart = () => {
   return false;
}

マウスによるドラッグ&ドロップとスマホにおけるタップによるドラッグ&ドロップを両方実装してます。

さてこれで移動できるか確認してみましょう。

ID5の人の施術時間をクリックしました。

スクリーンショット 2020-11-01 20.37.39

画面右上までドラッグ&ドロップできました。

スクリーンショット 2020-11-01 20.38.22

ではスマホはどうでしょうか。タップした時の画像です。

画像5

タップによるドラッグ&ドロップでここまで持っていけました。

画像6

今回はここまでとしたいと思います。

次回は、編集ボタンと削除ボタンの処理をコーディングし、実際に編集データをサーバ側へPOSTする処理を実装していきたいと思います。

本記事が少しでも参考になりましたら「スキ」をいただけると嬉しいです。

MENTAでもプログラミングのコーチや開発支援をさせていただいております。お気軽にお問合せください。

最後までお読みいただき、ありがとうございました。

この記事が気に入ったらサポートをしてみませんか?