見出し画像

【Rails,JavaScript】画面遷移時にチェックボックスの状態を保持できるようにする

背景

Railsでエリア選択機能を作っていて、都道府県名が表示されたタブをクリックすると、表示されるエリアの一覧(例えば渋谷とか銀座など)が変わるという内容の実装をしていた。
都道府県は47つあり、display: block;とnoneを切り替える実装方法は難しいなと思ったので、エリア一覧部分のテンプレートを作って、都道府県名のタブをクリックしたら、js.erbでそのテンプレートだけを切り替えるという方法で実装した。

ただそうすると、チェックボックスのチェックがタブを切り替えるごとに外れてしまう。(選択状態を保持できない)
今回は、都道府県を跨いで複数エリアを選択できるようにしたいので、画面遷移しても選択状態を保持できるようにしたい。
今回はその解決方法を書いていく。

方法としては、「チェックが外れないようにする」というよりは、
チェックをつけた時点でどこかに選択状況を保存しておいて、部分テンプレートの読み込み時に選択状況を都度判定してチェックをつけるという方法で実装する。

保存先として利用するのはjavascriptのsessionStorage
https://developer.mozilla.org/ja/docs/Web/API/Window/sessionStorage

全体のコード

function saveAreaSessionStorage() {
 $(document).on('change', '.select-area-check-box', function() {
   const alreadySelectedArea = JSON.parse(sessionStorage.getItem("area_id")); // 既に選択中の値の配列
   const selectedArea = $(this).val(); // 新しく選択した値
   const selectedAreaId = [Number(selectedArea)]; // selectedAreaを配列の形にしておく
   if ( alreadySelectedArea ){
         if (alreadySelectedArea.includes(selectedAreaId[0])){  // 既存の値の配列に今選択した値が含まれるか判定する
           newSelectedArea = getArrayDiff(alreadySelectedArea, selectedAreaId) // 差分を計算
           sessionStorage.setItem("area_id", JSON.stringify(newSelectedArea));
           if (JSON.parse(sessionStorage.getItem("area_id")).length === 0) {
             sessionStorage.removeItem("area_id"); //値が入っていなかったらarea_idのキー自体を消しておく
           }
         } else {
           const joinedSelectedArea = alreadySelectedArea.concat(selectedAreaId).flat(); // 既存の配列と今選択した値をがっちゃんこ
           const removeEmptyElemSelectedArea = joinedSelectedArea.filter(v => v); // 空の値を取り除く
           const eliminateDuplicationSelectedArea = removeEmptyElemSelectedArea.filter((x, i, self) => self.indexOf(x) === i); // 重複を取り除く
           sessionStorage.setItem("area_id", JSON.stringify(eliminateDuplicationSelectedArea));
         }
   } else {
       sessionStorage.setItem("area_id", JSON.stringify(selectedAreaId));
   }
 });
}
$(function(){
 const area_ids = JSON.parse(sessionStorage.getItem("area_id"));
 if (area_ids != null) {
   for (const area_id of area_ids ) {
     const id = ('selected-area-' + (area_id));
     if (document.getElementById(id)){
       document.getElementById(id).checked = true;
     }
   }
 }
});
$(function(){
 saveAreaSessionStorage();
});

おおまかな流れ

①タブ切り替え機能を実装する (今回は省略)
②チェックをつけたら選択内容をsessionStorageのarea_id(キー)に保存する処理を書く
③遷移時にsessionStorageに保存中のarea_idの値を判定して、該当のチェックボックスにチェックをつける
④送信する時はsessionStorageのarea_idの選択状況を隠しフォームに入れて送信する

①は長くなりそうなので別の機会に書くか、今後ここに追記します。

②チェックをつけたら選択内容をsessionStorageのarea_id(キー)に保存する処理を書く

function saveAreaSessionStorage() {
 $(document).on('change', '.area-check-box', function() {
   const alreadySelectedArea = JSON.parse(sessionStorage.getItem("area_id"));  // 既に選択中の値の配列
   const selectedArea = $(this).val();  // 新しく選択した値
   const selectedAreaId = [Number(selectedArea)];  // selectedAreaを配列の形にしておく
   if ( alreadySelectedArea ){
         if (alreadySelectedArea.includes(selectedAreaId[0])){
           newSelectedArea = getArrayDiff(alreadySelectedArea, selectedAreaId) // 既存の値の配列に今選択した値が含まれるか判定する
           sessionStorage.setItem("area_id", JSON.stringify(newSelectedArea));
           if (JSON.parse(sessionStorage.getItem("area_id")).length === 0) {
             sessionStorage.removeItem("area_id");  //値が入っていなかったらarea_idのキー自体を消しておく
           }
         } else {
           const joinedSelectedArea = alreadySelectedArea.concat(selectedAreaId).flat(); // 既存の配列と今選択した値をがっちゃんこ
           const removeEmptyElemSelectedArea = joinedSelectedArea.filter(v => v); // 空の値を取り除く
           const eliminateDuplicationSelectedArea = removeEmptyElemSelectedArea.filter((x, i, self) => self.indexOf(x) === i);  // 重複を取り除く
           sessionStorage.setItem("area_id", JSON.stringify(eliminateDuplicationSelectedArea)); 
   } else {
       sessionStorage.setItem("area_id", JSON.stringify(selectedAreaId));
   }
 });
}

上のコードは、ざっくりと下記のことを行っている。

・既に選択中の値と、新たに選択した値を、それぞれ配列の形で定義
・sessionStorageに"area_id"というキーがなければ、選択した値を追加する
・sessionStorageに"area_id"というキーがあり、今選択したarea_idの値が既に保存されていなければ、追加する 
・sessionStorageに"area_id"というキーがあり、今選択したarea_idの値が既に保存されていればsessionStorageからそのarea_idの値を削除する

// 配列同士の減算のメソッド 値を削除する時に使う
function getArrayDiff(arr1, arr2) {
 let arr = arr1.concat(arr2);
 return arr.filter((v, i)=> {
   return !(arr1.indexOf(v) !== -1 && arr2.indexOf(v) !== -1);
 });
}

④、遷移時にsessionStorageに保存中のarea_idの値を判定して、該当のチェックボックスにチェックをつける

$(function(){
 const area_ids = JSON.parse(sessionStorage.getItem("area_id"));
 if (area_ids != null) {
   for (const area_id of area_ids ) {
     const id = ('selected-area-' + (area_id)); 
     if (document.getElementById(id)){
       document.getElementById(id).checked = true;
     }
   }
 }
});

sessionStorageに保存されているarea_idと一致するチェックボックスにチェックをつける

⑤、送信する時はsessionStorageのarea_idの選択状況を隠しフォームに入れて送信する

// ~~.html.erb
<%= form_with url: hoge, method: "post", id: "area-select-form", local: true do |form| %>
 <%= form.hidden_field :selected_area, value: "", id: "select-area-hidden-form", name: "selected_area" %>
 <div>
   <button type="button"  class="select-btn" id="area-select-submit-btn">選択する</button>
 </div>
<% end %>

valueが空のhidden_fieldを設置する。

// ~~.js
$(function(){
 $('#area-select-submit-btn').click(function(){
   document.getElementById('select-area-hidden-form').value = JSON.parse(sessionStorage.getItem('area_id'));
   $('#area-select-form').submit();
   sessionStorage.removeItem('area_id') // 送信と同時にsessionStorageは消しておく
 });
})

送信ボタンをクリックしたら、hidden_fieldにsessionStorageのarea_idの値を入れる。

document.getElementById('select-area-hidden-form').value = JSON.parse(sessionStorage.getItem('area_id'));

その後、$('#area-select-form').submit();で値を送信する。

その他

エリア選択画面以外ではsessionStorageのarea_idは使わないので、消す処理を書いておく。

if(!document.URL.match("エリア選択画面のurl")) {
 sessionStorage.removeItem('area_id')
}

いいなと思ったら応援しよう!